< 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>
 330821        {
 330822            if (ipSpan.Contains(TChar.CreateTruncating(':')))
 186823            {
 186824                return IPv6AddressHelper.IsValidStrict(ipSpan);
 25            }
 26            else
 144027            {
 144028                long address = IPv4AddressHelper.ParseNonCanonical(ipSpan, out int end, notImplicitFile: true);
 144029                return address != IPv4AddressHelper.Invalid && end == ipSpan.Length;
 30            }
 330831        }
 32
 33        internal static unsafe IPAddress? Parse<TChar>(ReadOnlySpan<TChar> ipSpan, bool tryParse)
 34            where TChar : unmanaged, IBinaryInteger<TChar>
 276035        {
 276036            Debug.Assert(typeof(TChar) == typeof(byte) || typeof(TChar) == typeof(char));
 37
 276038            if (ipSpan.Contains(TChar.CreateTruncating(':')))
 142539            {
 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.
 142542                Span<ushort> numbers = stackalloc ushort[IPAddressParserStatics.IPv6AddressShorts];
 142543                numbers.Clear();
 142544                if (TryParseIPv6(ipSpan, numbers, IPAddressParserStatics.IPv6AddressShorts, out uint scope))
 88645                {
 88646                    return new IPAddress(numbers, scope);
 47                }
 53948            }
 133549            else if (TryParseIpv4(ipSpan, out long address))
 21050            {
 21051                return new IPAddress(address);
 52            }
 53
 166454            if (tryParse)
 166455            {
 166456                return null;
 57            }
 58
 059            throw new FormatException(SR.dns_bad_ip_address, new SocketException(SocketError.InvalidArgument));
 276060        }
 61
 62        private static bool TryParseIpv4<TChar>(ReadOnlySpan<TChar> ipSpan, out long address)
 63            where TChar : unmanaged, IBinaryInteger<TChar>
 133564        {
 133565            long tmpAddr = IPv4AddressHelper.ParseNonCanonical(ipSpan, out int end, notImplicitFile: true);
 66
 133567            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.
 112576            address = 0;
 112577            return false;
 133578        }
 79
 80        private static bool TryParseIPv6<TChar>(ReadOnlySpan<TChar> ipSpan, Span<ushort> numbers, int numbersLength, out
 81            where TChar : unmanaged, IBinaryInteger<TChar>
 142582        {
 142583            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 142584            Debug.Assert(numbersLength >= IPAddressParserStatics.IPv6AddressShorts);
 85
 142586            if (!IPv6AddressHelper.IsValidStrict(ipSpan))
 53987            {
 53988                scope = 0;
 53989                return false;
 90            }
 91
 88692            IPv6AddressHelper.Parse(ipSpan, numbers, out ReadOnlySpan<TChar> scopeIdSpan);
 93
 88694            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
 854132            scope = 0;
 854133            return true;
 1425134        }
 135
 136        internal static int FormatIPv4Address<TChar>(uint address, Span<TChar> addressString)
 137            where TChar : unmanaged, IBinaryInteger<TChar>
 152138        {
 152139            address = (uint)IPAddress.NetworkToHostOrder(unchecked((int)address));
 140
 152141            int pos = FormatByte(address >> 24, addressString);
 152142            addressString[pos++] = TChar.CreateTruncating('.');
 152143            pos += FormatByte(address >> 16, addressString.Slice(pos));
 152144            addressString[pos++] = TChar.CreateTruncating('.');
 152145            pos += FormatByte(address >> 8, addressString.Slice(pos));
 152146            addressString[pos++] = TChar.CreateTruncating('.');
 152147            pos += FormatByte(address, addressString.Slice(pos));
 148
 152149            return pos;
 150
 151            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 152            static int FormatByte(uint number, Span<TChar> addressString)
 608153            {
 608154                number &= 0xFF;
 155
 608156                if (number >= 10)
 158157                {
 158                    uint hundreds, tens;
 158159                    if (number >= 100)
 54160                    {
 54161                        (uint hundredsAndTens, number) = Math.DivRem(number, 10);
 54162                        (hundreds, tens) = Math.DivRem(hundredsAndTens, 10);
 163
 54164                        addressString[2] = TChar.CreateTruncating('0' + number);
 54165                        addressString[1] = TChar.CreateTruncating('0' + tens);
 54166                        addressString[0] = TChar.CreateTruncating('0' + hundreds);
 54167                        return 3;
 168                    }
 169
 104170                    (tens, number) = Math.DivRem(number, 10);
 104171                    addressString[1] = TChar.CreateTruncating('0' + number);
 104172                    addressString[0] = TChar.CreateTruncating('0' + tens);
 104173                    return 2;
 174                }
 175
 450176                addressString[0] = TChar.CreateTruncating('0' + number);
 450177                return 1;
 608178            }
 152179        }
 180
 181        internal static int FormatIPv6Address<TChar>(ushort[] address, uint scopeId, Span<TChar> destination)
 182            where TChar : unmanaged, IBinaryInteger<TChar>
 443183        {
 443184            Debug.Assert(typeof(TChar) == typeof(byte) || typeof(TChar) == typeof(char));
 443185            int pos = 0;
 186
 443187            if (IPv6AddressHelper.ShouldHaveIpv4Embedded(address))
 47188            {
 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.
 47191                AppendSections(address.AsSpan(0, 6), destination, ref pos);
 47192                if (destination[pos - 1] != TChar.CreateTruncating(':'))
 0193                {
 0194                    destination[pos++] = TChar.CreateTruncating(':');
 0195                }
 196
 47197                pos += FormatIPv4Address(ExtractIPv4Address(address), destination.Slice(pos));
 47198            }
 199            else
 396200            {
 201                // No IPv4 address.  Output all 8 sections as part of the IPv6 address
 202                // with normal formatting rules.
 396203                AppendSections(address.AsSpan(0, 8), destination, ref pos);
 396204            }
 205
 206            // If there's a scope ID, append it.
 443207            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
 443220            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)
 443226            {
 227                // Find the longest sequence of zeros to be combined into a "::"
 443228                (int zeroStart, int zeroEnd) = IPv6AddressHelper.FindCompressionRange(address);
 443229                bool needsColon = false;
 230
 231                // Handle a zero sequence if there is one
 443232                if (zeroStart >= 0)
 384233                {
 234                    // Output all of the numbers before the zero sequence
 1702235                    for (int i = 0; i < zeroStart; i++)
 467236                    {
 467237                        if (needsColon)
 283238                        {
 283239                            destination[offset++] = TChar.CreateTruncating(':');
 283240                        }
 467241                        needsColon = true;
 467242                        AppendHex(address[i], destination, ref offset);
 467243                    }
 244
 245                    // Output the zero sequence if there is one
 384246                    destination[offset++] = TChar.CreateTruncating(':');
 384247                    destination[offset++] = TChar.CreateTruncating(':');
 384248                    needsColon = false;
 384249                }
 250
 251                // Output everything after the zero sequence
 2674252                for (int i = zeroEnd; i < address.Length; i++)
 894253                {
 894254                    if (needsColon)
 663255                    {
 663256                        destination[offset++] = TChar.CreateTruncating(':');
 663257                    }
 894258                    needsColon = true;
 894259                    AppendHex(address[i], destination, ref offset);
 894260                }
 443261            }
 262
 263            // Appends a number as hexadecimal (without the leading "0x")
 264            static void AppendHex(ushort value, Span<TChar> destination, ref int offset)
 1361265            {
 1361266                if ((value & 0xFFF0) != 0)
 695267                {
 695268                    if ((value & 0xFF00) != 0)
 343269                    {
 343270                        if ((value & 0xF000) != 0)
 103271                        {
 103272                            destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value >> 12));
 103273                        }
 274
 343275                        destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value >> 8));
 343276                    }
 277
 695278                    destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value >> 4));
 695279                }
 280
 1361281                destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value));
 1361282            }
 443283        }
 284
 285        /// <summary>Extracts the IPv4 address from the end of the IPv6 address byte array.</summary>
 286        private static uint ExtractIPv4Address(ushort[] address)
 47287        {
 47288            uint ipv4address = (uint)address[6] << 16 | address[7];
 47289            return (uint)IPAddress.HostToNetworkOrder(unchecked((int)ipv4address));
 47290        }
 291    }
 292}