< Summary

Information
Class: System.Net.IPAddressParser
Assembly: System.Net.Primitives
File(s): D:\runner\runtime\src\libraries\System.Net.Primitives\src\System\Net\IPAddressParser.cs
Line coverage
97%
Covered lines: 168
Uncovered lines: 4
Coverable lines: 172
Total lines: 292
Line coverage: 97.6%
Branch coverage
93%
Covered branches: 56
Total branches: 60
Branch coverage: 93.3%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

D:\runner\runtime\src\libraries\System.Net.Primitives\src\System\Net\IPAddressParser.cs

#LineLine coverage
 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
 4using System.Diagnostics;
 5using System.Globalization;
 6using System.Net.NetworkInformation;
 7using System.Net.Sockets;
 8using System.Numerics;
 9using System.Runtime.CompilerServices;
 10using System.Runtime.InteropServices;
 11
 12namespace System.Net
 13{
 14    internal static class IPAddressParser
 15    {
 16        internal const int MaxIPv4StringLength = 15; // 4 numbers separated by 3 periods, with up to 3 digits per number
 17        internal const int MaxIPv6StringLength = 65;
 18
 19        public static bool IsValid<TChar>(ReadOnlySpan<TChar> ipSpan)
 20            where TChar : unmanaged, IBinaryInteger<TChar>
 344621        {
 344622            if (ipSpan.Contains(TChar.CreateTruncating(':')))
 196323            {
 196324                return IPv6AddressHelper.IsValidStrict(ipSpan);
 25            }
 26            else
 148327            {
 148328                long address = IPv4AddressHelper.ParseNonCanonical(ipSpan, out int end, notImplicitFile: true);
 148329                return address != IPv4AddressHelper.Invalid && end == ipSpan.Length;
 30            }
 344631        }
 32
 33        internal static unsafe IPAddress? Parse<TChar>(ReadOnlySpan<TChar> ipSpan, bool tryParse)
 34            where TChar : unmanaged, IBinaryInteger<TChar>
 287135        {
 287136            Debug.Assert(typeof(TChar) == typeof(byte) || typeof(TChar) == typeof(char));
 37
 287138            if (ipSpan.Contains(TChar.CreateTruncating(':')))
 149339            {
 40                // The address is parsed as IPv6 if and only if it contains a colon. This is valid because
 41                // we don't support/parse a port specification at the end of an IPv4 address.
 149342                Span<ushort> numbers = stackalloc ushort[IPAddressParserStatics.IPv6AddressShorts];
 149343                numbers.Clear();
 149344                if (TryParseIPv6(ipSpan, numbers, IPAddressParserStatics.IPv6AddressShorts, out uint scope))
 94045                {
 94046                    return new IPAddress(numbers, scope);
 47                }
 55348            }
 137849            else if (TryParseIpv4(ipSpan, out long address))
 21050            {
 21051                return new IPAddress(address);
 52            }
 53
 172154            if (tryParse)
 172155            {
 172156                return null;
 57            }
 58
 059            throw new FormatException(SR.dns_bad_ip_address, new SocketException(SocketError.InvalidArgument));
 287160        }
 61
 62        private static bool TryParseIpv4<TChar>(ReadOnlySpan<TChar> ipSpan, out long address)
 63            where TChar : unmanaged, IBinaryInteger<TChar>
 137864        {
 137865            long tmpAddr = IPv4AddressHelper.ParseNonCanonical(ipSpan, out int end, notImplicitFile: true);
 66
 137867            if (tmpAddr != IPv4AddressHelper.Invalid && end == ipSpan.Length)
 21068            {
 69                // IPv4AddressHelper.ParseNonCanonical returns the bytes in host order.
 70                // Convert to network order and return success.
 21071                address = (uint)IPAddress.HostToNetworkOrder(unchecked((int)tmpAddr));
 21072                return true;
 73            }
 74
 75            // Failed to parse the address.
 116876            address = 0;
 116877            return false;
 137878        }
 79
 80        private static bool TryParseIPv6<TChar>(ReadOnlySpan<TChar> ipSpan, Span<ushort> numbers, int numbersLength, out
 81            where TChar : unmanaged, IBinaryInteger<TChar>
 149382        {
 149383            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 149384            Debug.Assert(numbersLength >= IPAddressParserStatics.IPv6AddressShorts);
 85
 149386            if (!IPv6AddressHelper.IsValidStrict(ipSpan))
 55387            {
 55388                scope = 0;
 55389                return false;
 90            }
 91
 94092            IPv6AddressHelper.Parse(ipSpan, numbers, out ReadOnlySpan<TChar> scopeIdSpan);
 93
 94094            if (scopeIdSpan.Length > 1)
 21495            {
 96                bool parsedNumericScope;
 21497                scopeIdSpan = scopeIdSpan.Slice(1);
 98
 99                // scopeId is a numeric value
 214100                if (typeof(TChar) == typeof(byte))
 107101                {
 107102                    ReadOnlySpan<byte> castScopeIdSpan = MemoryMarshal.Cast<TChar, byte>(scopeIdSpan);
 103
 107104                    parsedNumericScope = uint.TryParse(castScopeIdSpan, NumberStyles.None, CultureInfo.InvariantCulture,
 107105                }
 106                else
 107107                {
 107108                    ReadOnlySpan<char> castScopeIdSpan = MemoryMarshal.Cast<TChar, char>(scopeIdSpan);
 109
 107110                    parsedNumericScope = uint.TryParse(castScopeIdSpan, NumberStyles.None, CultureInfo.InvariantCulture,
 107111                }
 112
 214113                if (parsedNumericScope)
 30114                {
 30115                    return true;
 116                }
 117                else
 184118                {
 184119                    uint interfaceIndex = InterfaceInfoPal.InterfaceNameToIndex(scopeIdSpan);
 120
 184121                    if (interfaceIndex > 0)
 2122                    {
 2123                        scope = interfaceIndex;
 2124                        return true; // scopeId is a known interface name
 125                    }
 182126                }
 127
 128                // scopeId is an unknown interface name
 182129            }
 130
 131            // scopeId is not presented
 908132            scope = 0;
 908133            return true;
 1493134        }
 135
 136        internal static int FormatIPv4Address<TChar>(uint address, Span<TChar> addressString)
 137            where TChar : unmanaged, IBinaryInteger<TChar>
 156138        {
 156139            address = (uint)IPAddress.NetworkToHostOrder(unchecked((int)address));
 140
 156141            int pos = FormatByte(address >> 24, addressString);
 156142            addressString[pos++] = TChar.CreateTruncating('.');
 156143            pos += FormatByte(address >> 16, addressString.Slice(pos));
 156144            addressString[pos++] = TChar.CreateTruncating('.');
 156145            pos += FormatByte(address >> 8, addressString.Slice(pos));
 156146            addressString[pos++] = TChar.CreateTruncating('.');
 156147            pos += FormatByte(address, addressString.Slice(pos));
 148
 156149            return pos;
 150
 151            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 152            static int FormatByte(uint number, Span<TChar> addressString)
 624153            {
 624154                number &= 0xFF;
 155
 624156                if (number >= 10)
 172157                {
 158                    uint hundreds, tens;
 172159                    if (number >= 100)
 56160                    {
 56161                        (uint hundredsAndTens, number) = Math.DivRem(number, 10);
 56162                        (hundreds, tens) = Math.DivRem(hundredsAndTens, 10);
 163
 56164                        addressString[2] = TChar.CreateTruncating('0' + number);
 56165                        addressString[1] = TChar.CreateTruncating('0' + tens);
 56166                        addressString[0] = TChar.CreateTruncating('0' + hundreds);
 56167                        return 3;
 168                    }
 169
 116170                    (tens, number) = Math.DivRem(number, 10);
 116171                    addressString[1] = TChar.CreateTruncating('0' + number);
 116172                    addressString[0] = TChar.CreateTruncating('0' + tens);
 116173                    return 2;
 174                }
 175
 452176                addressString[0] = TChar.CreateTruncating('0' + number);
 452177                return 1;
 624178            }
 156179        }
 180
 181        internal static int FormatIPv6Address<TChar>(ushort[] address, uint scopeId, Span<TChar> destination)
 182            where TChar : unmanaged, IBinaryInteger<TChar>
 470183        {
 470184            Debug.Assert(typeof(TChar) == typeof(byte) || typeof(TChar) == typeof(char));
 470185            int pos = 0;
 186
 470187            if (IPv6AddressHelper.ShouldHaveIpv4Embedded(address))
 51188            {
 189                // We need to treat the last 2 ushorts as a 4-byte IPv4 address,
 190                // so output the first 6 ushorts normally, followed by the IPv4 address.
 51191                AppendSections(address.AsSpan(0, 6), destination, ref pos);
 51192                if (destination[pos - 1] != TChar.CreateTruncating(':'))
 0193                {
 0194                    destination[pos++] = TChar.CreateTruncating(':');
 0195                }
 196
 51197                pos += FormatIPv4Address(ExtractIPv4Address(address), destination.Slice(pos));
 51198            }
 199            else
 419200            {
 201                // No IPv4 address.  Output all 8 sections as part of the IPv6 address
 202                // with normal formatting rules.
 419203                AppendSections(address.AsSpan(0, 8), destination, ref pos);
 419204            }
 205
 206            // If there's a scope ID, append it.
 470207            if (scopeId != 0)
 16208            {
 16209                destination[pos++] = TChar.CreateTruncating('%');
 210
 211                int bytesWritten;
 16212                bool formatted = typeof(TChar) == typeof(byte) ?
 16213                    scopeId.TryFormat(MemoryMarshal.Cast<TChar, byte>(destination).Slice(pos), out bytesWritten) :
 16214                    scopeId.TryFormat(MemoryMarshal.Cast<TChar, char>(destination).Slice(pos), out bytesWritten);
 215
 16216                Debug.Assert(formatted);
 16217                pos += bytesWritten;
 16218            }
 219
 470220            return pos;
 221
 222            // Appends each of the numbers in address in indexed range [fromInclusive, toExclusive),
 223            // while also replacing the longest sequence of 0s found in that range with "::", as long
 224            // as the sequence is more than one 0.
 225            static void AppendSections(ReadOnlySpan<ushort> address, Span<TChar> destination, ref int offset)
 470226            {
 227                // Find the longest sequence of zeros to be combined into a "::"
 470228                (int zeroStart, int zeroEnd) = IPv6AddressHelper.FindCompressionRange(address);
 470229                bool needsColon = false;
 230
 231                // Handle a zero sequence if there is one
 470232                if (zeroStart >= 0)
 410233                {
 234                    // Output all of the numbers before the zero sequence
 1906235                    for (int i = 0; i < zeroStart; i++)
 543236                    {
 543237                        if (needsColon)
 341238                        {
 341239                            destination[offset++] = TChar.CreateTruncating(':');
 341240                        }
 543241                        needsColon = true;
 543242                        AppendHex(address[i], destination, ref offset);
 543243                    }
 244
 245                    // Output the zero sequence if there is one
 410246                    destination[offset++] = TChar.CreateTruncating(':');
 410247                    destination[offset++] = TChar.CreateTruncating(':');
 410248                    needsColon = false;
 410249                }
 250
 251                // Output everything after the zero sequence
 2772252                for (int i = zeroEnd; i < address.Length; i++)
 916253                {
 916254                    if (needsColon)
 677255                    {
 677256                        destination[offset++] = TChar.CreateTruncating(':');
 677257                    }
 916258                    needsColon = true;
 916259                    AppendHex(address[i], destination, ref offset);
 916260                }
 470261            }
 262
 263            // Appends a number as hexadecimal (without the leading "0x")
 264            static void AppendHex(ushort value, Span<TChar> destination, ref int offset)
 1459265            {
 1459266                if ((value & 0xFFF0) != 0)
 711267                {
 711268                    if ((value & 0xFF00) != 0)
 347269                    {
 347270                        if ((value & 0xF000) != 0)
 106271                        {
 106272                            destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value >> 12));
 106273                        }
 274
 347275                        destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value >> 8));
 347276                    }
 277
 711278                    destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value >> 4));
 711279                }
 280
 1459281                destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value));
 1459282            }
 470283        }
 284
 285        /// <summary>Extracts the IPv4 address from the end of the IPv6 address byte array.</summary>
 286        private static uint ExtractIPv4Address(ushort[] address)
 51287        {
 51288            uint ipv4address = (uint)address[6] << 16 | address[7];
 51289            return (uint)IPAddress.HostToNetworkOrder(unchecked((int)ipv4address));
 51290        }
 291    }
 292}