|
1 | 1 | // Licensed to the .NET Foundation under one or more agreements.
|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
3 | 3 |
|
| 4 | +using System.Diagnostics; |
4 | 5 | using System.Diagnostics.CodeAnalysis;
|
5 | 6 | using System.Globalization;
|
6 | 7 | using System.Net.Sockets;
|
| 8 | +using System.Numerics; |
| 9 | +using System.Runtime.InteropServices; |
| 10 | +using System.Text.Unicode; |
7 | 11 |
|
8 | 12 | namespace System.Net
|
9 | 13 | {
|
10 | 14 | /// <summary>
|
11 | 15 | /// Provides an IP address.
|
12 | 16 | /// </summary>
|
13 |
| - public class IPEndPoint : EndPoint |
| 17 | + public class IPEndPoint : EndPoint, ISpanFormattable, ISpanParsable<IPEndPoint>, IUtf8SpanFormattable, IUtf8SpanParsable<IPEndPoint> |
14 | 18 | {
|
15 | 19 | /// <summary>
|
16 | 20 | /// Specifies the minimum acceptable value for the <see cref='System.Net.IPEndPoint.Port'/> property.
|
@@ -92,41 +96,69 @@ public static bool TryParse(string s, [NotNullWhen(true)] out IPEndPoint? result
|
92 | 96 | return TryParse(s.AsSpan(), out result);
|
93 | 97 | }
|
94 | 98 |
|
95 |
| - public static bool TryParse(ReadOnlySpan<char> s, [NotNullWhen(true)] out IPEndPoint? result) |
| 99 | + internal static bool InternalTryParse<TChar>(ReadOnlySpan<TChar> s, [NotNullWhen(true)] out IPEndPoint? result) |
| 100 | + where TChar : unmanaged, IBinaryInteger<TChar> |
96 | 101 | {
|
| 102 | + Debug.Assert(typeof(TChar) == typeof(byte) || typeof(TChar) == typeof(char)); |
| 103 | + |
97 | 104 | int addressLength = s.Length; // If there's no port then send the entire string to the address parser
|
98 |
| - int lastColonPos = s.LastIndexOf(':'); |
| 105 | + int lastColonPos = s.LastIndexOf(TChar.CreateTruncating(':')); |
99 | 106 |
|
100 | 107 | // Look to see if this is an IPv6 address with a port.
|
101 | 108 | if (lastColonPos > 0)
|
102 | 109 | {
|
103 |
| - if (s[lastColonPos - 1] == ']') |
| 110 | + if (s[lastColonPos - 1] == TChar.CreateTruncating(']')) |
104 | 111 | {
|
105 | 112 | addressLength = lastColonPos;
|
106 | 113 | }
|
107 | 114 | // Look to see if this is IPv4 with a port (IPv6 will have another colon)
|
108 |
| - else if (s.Slice(0, lastColonPos).LastIndexOf(':') == -1) |
| 115 | + else if (s.Slice(0, lastColonPos).LastIndexOf(TChar.CreateTruncating(':')) == -1) |
109 | 116 | {
|
110 | 117 | addressLength = lastColonPos;
|
111 | 118 | }
|
112 | 119 | }
|
113 | 120 |
|
114 |
| - if (IPAddress.TryParse(s.Slice(0, addressLength), out IPAddress? address)) |
| 121 | + IPAddress? address = IPAddressParser.Parse(s.Slice(0, addressLength), true); |
| 122 | + if (address is not null) |
115 | 123 | {
|
116 |
| - uint port = 0; |
117 |
| - if (addressLength == s.Length || |
118 |
| - (uint.TryParse(s.Slice(addressLength + 1), NumberStyles.None, CultureInfo.InvariantCulture, out port) && port <= MaxPort)) |
119 |
| - |
| 124 | + if (addressLength == s.Length) |
120 | 125 | {
|
121 |
| - result = new IPEndPoint(address, (int)port); |
| 126 | + result = new IPEndPoint(address, 0); |
122 | 127 | return true;
|
123 | 128 | }
|
| 129 | + else |
| 130 | + { |
| 131 | + uint port; |
| 132 | + ReadOnlySpan<TChar> portSpan = s.Slice(addressLength + 1); |
| 133 | + bool isConvertedToInt; |
| 134 | + |
| 135 | + if (typeof(TChar) == typeof(byte)) |
| 136 | + { |
| 137 | + isConvertedToInt = uint.TryParse(MemoryMarshal.Cast<TChar, byte>(portSpan), NumberStyles.None, CultureInfo.InvariantCulture, out port); |
| 138 | + } |
| 139 | + else |
| 140 | + { |
| 141 | + isConvertedToInt = uint.TryParse(MemoryMarshal.Cast<TChar, char>(portSpan), NumberStyles.None, CultureInfo.InvariantCulture, out port); |
| 142 | + } |
| 143 | + |
| 144 | + if (isConvertedToInt && port <= MaxPort) |
| 145 | + { |
| 146 | + result = new IPEndPoint(address, (int)port); |
| 147 | + return true; |
| 148 | + } |
| 149 | + } |
124 | 150 | }
|
125 | 151 |
|
126 | 152 | result = null;
|
127 | 153 | return false;
|
128 | 154 | }
|
129 | 155 |
|
| 156 | + /// <summary>Tries to convert the character span to its <see cref="IPEndPoint"/> equivalent.</summary> |
| 157 | + /// <param name="s">A span container the characters representing the <see cref="IPEndPoint"/> to convert.</param> |
| 158 | + /// <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> |
| 159 | + /// <returns><c>true</c> if <paramref name="s" /> was converted successfully; otherwise, false.</returns> |
| 160 | + public static bool TryParse(ReadOnlySpan<char> s, [NotNullWhen(true)] out IPEndPoint? result) => InternalTryParse(s, out result); |
| 161 | + |
130 | 162 | public static IPEndPoint Parse(string s)
|
131 | 163 | {
|
132 | 164 | ArgumentNullException.ThrowIfNull(s);
|
@@ -178,5 +210,118 @@ public override int GetHashCode()
|
178 | 210 | {
|
179 | 211 | return _address.GetHashCode() ^ _port;
|
180 | 212 | }
|
| 213 | + |
| 214 | + /// <summary>Converts the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary> |
| 215 | + /// <param name="utf8Text">A span containing the characters representing the <see cref="IPEndPoint"/> to convert.</param> |
| 216 | + /// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded</returns> |
| 217 | + /// <exception cref="FormatException"><paramref name="utf8Text"/> is invalid</exception> |
| 218 | + public static IPEndPoint Parse(ReadOnlySpan<byte> utf8Text) |
| 219 | + { |
| 220 | + if (TryParse(utf8Text, out IPEndPoint? result)) |
| 221 | + { |
| 222 | + return result; |
| 223 | + } |
| 224 | + |
| 225 | + throw new FormatException(SR.bad_endpoint_string); |
| 226 | + } |
| 227 | + |
| 228 | + /// <summary>Converts the character span to its <see cref="IPEndPoint"/> equivalent.</summary> |
| 229 | + /// <param name="s">A span containing the characters representing the <see cref="IPEndPoint"/> to convert.</param> |
| 230 | + /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param> |
| 231 | + /// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded</returns> |
| 232 | + /// <exception cref="FormatException"><paramref name="s"/> is invalid</exception> |
| 233 | + static IPEndPoint ISpanParsable<IPEndPoint>.Parse(ReadOnlySpan<char> s, IFormatProvider? provider) => Parse(s); |
| 234 | + |
| 235 | + /// <summary>Converts the string to its <see cref="IPEndPoint"/> equivalent.</summary> |
| 236 | + /// <param name="s">A string containing the characters representing the <see cref="IPEndPoint"/> to convert.</param> |
| 237 | + /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param> |
| 238 | + /// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="s" /> if the conversion succeeded</returns> |
| 239 | + /// <exception cref="FormatException"><paramref name="s"/> is invalid</exception> |
| 240 | + static IPEndPoint IParsable<IPEndPoint>.Parse(string s, IFormatProvider? provider) => Parse(s); |
| 241 | + |
| 242 | + /// <summary>Converts the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary> |
| 243 | + /// <param name="utf8Text">A Span containing the UTF-8 characters representing the <see cref="IPEndPoint"/> to convert.</param> |
| 244 | + /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="utf8Text" />.</param> |
| 245 | + /// <returns>contains the <see cref="IPEndPoint"/> value equivalent to what is contained in <paramref name="utf8Text" /> if the conversion succeeded</returns> |
| 246 | + /// <exception cref="FormatException"><paramref name="utf8Text"/> is invalid</exception> |
| 247 | + static IPEndPoint IUtf8SpanParsable<IPEndPoint>.Parse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider) => Parse(utf8Text); |
| 248 | + |
| 249 | + /// <summary>Tries to convert the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary> |
| 250 | + /// <param name="utf8Text">A span containing the UTF-8 characters representing the <see cref="IPEndPoint"/> to convert.</param> |
| 251 | + /// <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> |
| 252 | + /// <returns><c>true</c> if <paramref name="utf8Text" /> was converted successfully; otherwise, false.</returns> |
| 253 | + public static bool TryParse(ReadOnlySpan<byte> utf8Text, [NotNullWhen(true)] out IPEndPoint? result) => InternalTryParse(utf8Text, out result); |
| 254 | + |
| 255 | + /// <summary>Tries to convert the character span to its <see cref="IPEndPoint"/> equivalent.</summary> |
| 256 | + /// <param name="s">A span container the characters representing the <see cref="IPEndPoint"/> to convert.</param> |
| 257 | + /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param> |
| 258 | + /// <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> |
| 259 | + /// <returns><c>true</c> if <paramref name="s" /> was converted successfully; otherwise, false.</returns> |
| 260 | + static bool ISpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result) => TryParse(s, out result); |
| 261 | + |
| 262 | + /// <summary>Tries to convert the string to its <see cref="IPEndPoint"/> equivalent.</summary> |
| 263 | + /// <param name="s">A string representing the <see cref="IPEndPoint"/> to convert.</param> |
| 264 | + /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="s" />.</param> |
| 265 | + /// <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> |
| 266 | + /// <returns><c>true</c> if <paramref name="s" /> was converted successfully; otherwise, false.</returns> |
| 267 | + static bool IParsable<IPEndPoint>.TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result) |
| 268 | + { |
| 269 | + if (s is null) |
| 270 | + { |
| 271 | + result = default; |
| 272 | + return false; |
| 273 | + } |
| 274 | + |
| 275 | + return TryParse(s, out result); |
| 276 | + } |
| 277 | + |
| 278 | + /// <summary>Tries to convert the UTF-8 span to its <see cref="IPEndPoint"/> equivalent.</summary> |
| 279 | + /// <param name="utf8Text">A span container the characters representing the <see cref="IPEndPoint"/> to convert.</param> |
| 280 | + /// <param name="provider">An object that provides culture-specific formatting information about <paramref name="utf8Text" />.</param> |
| 281 | + /// <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> |
| 282 | + /// <returns><c>true</c> if <paramref name="utf8Text" /> was converted successfully; otherwise, false.</returns> |
| 283 | + static bool IUtf8SpanParsable<IPEndPoint>.TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, [NotNullWhen(true)] out IPEndPoint? result) => TryParse(utf8Text, out result); |
| 284 | + |
| 285 | + /// <summary>Returns the string representation of the current instance using the specified format string to define culture-specific formatting.</summary> |
| 286 | + /// <param name="format">A standard or custom numeric format string that defines the format of individual elements.</param> |
| 287 | + /// <param name="formatProvider">A format provider that supplies culture-specific formatting information.</param> |
| 288 | + /// <returns>The string representation of the current instance.</returns> |
| 289 | + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString(); |
| 290 | + |
| 291 | + /// <summary>Tries to format the value of the current instance as characters into the provided span of characters.</summary> |
| 292 | + /// <param name="destination">When this method returns, this parameter is filled with this instance formatted characters.</param> |
| 293 | + /// <param name="charsWritten">When this method returns, the number of bytes that were written in <paramref name="destination"/>.</param> |
| 294 | + /// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns> |
| 295 | + public bool TryFormat(Span<char> destination, out int charsWritten) => |
| 296 | + _address.AddressFamily == AddressFamily.InterNetworkV6 ? |
| 297 | + destination.TryWrite(CultureInfo.InvariantCulture, $"[{_address}]:{_port}", out charsWritten) : |
| 298 | + destination.TryWrite(CultureInfo.InvariantCulture, $"{_address}:{_port}", out charsWritten); |
| 299 | + |
| 300 | + /// <summary>Tries to format the value of the current instance as UTF-8 bytes into the provided span.</summary> |
| 301 | + /// <param name="utf8Destination">When this method returns, this parameter is filled with this instance formatted UTF68 bytes.</param> |
| 302 | + /// <param name="bytesWritten">When this method returns, the number of bytes that were written in <paramref name="utf8Destination"/>.</param> |
| 303 | + /// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns> |
| 304 | + public bool TryFormat(Span<byte> utf8Destination, out int bytesWritten) => |
| 305 | + _address.AddressFamily == AddressFamily.InterNetworkV6 ? |
| 306 | + Utf8.TryWrite(utf8Destination, CultureInfo.InvariantCulture, $"[{_address}]:{_port}", out bytesWritten) : |
| 307 | + Utf8.TryWrite(utf8Destination, CultureInfo.InvariantCulture, $"{_address}:{_port}", out bytesWritten); |
| 308 | + |
| 309 | + /// <summary>Tries to format the value of the current instance as characters into the provided span of characters.</summary> |
| 310 | + /// <param name="destination">When this method returns, this parameter is filled with this instance formatted characters.</param> |
| 311 | + /// <param name="charsWritten">When this method returns, the number of bytes that were written in <paramref name="destination"/>.</param> |
| 312 | + /// <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> |
| 313 | + /// <param name="provider">An optional object that supplies culture-specific formatting information for <paramref name="destination"/>.</param> |
| 314 | + /// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns> |
| 315 | + bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) => |
| 316 | + TryFormat(destination, out charsWritten); |
| 317 | + |
| 318 | + /// <summary>Tries to format the value of the current instance as UTF-8 bytes into the provided span.</summary> |
| 319 | + /// <param name="utf8Destination">When this method returns, this parameter is filled with this instance formatted UTF68 bytes.</param> |
| 320 | + /// <param name="bytesWritten">When this method returns, the number of bytes that were written in <paramref name="utf8Destination"/>.</param> |
| 321 | + /// <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> |
| 322 | + /// <param name="provider">An optional object that supplies culture-specific formatting information for <paramref name="utf8Destination"/>.</param> |
| 323 | + /// <returns><see langword="true"/> if the formatting was successful; otherwise, <see langword="false"/>.</returns> |
| 324 | + bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) => |
| 325 | + TryFormat(utf8Destination, out bytesWritten); |
181 | 326 | }
|
182 | 327 | }
|
0 commit comments