< Summary

Line coverage
97%
Covered lines: 168
Uncovered lines: 4
Coverable lines: 172
Total lines: 293
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)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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>
 419221        {
 419222            if (ipSpan.Contains(TChar.CreateTruncating(':')))
 238923            {
 238924                return IPv6AddressHelper.IsValidStrict(ipSpan);
 25            }
 26            else
 180327            {
 180328                long address = IPv4AddressHelper.ParseNonCanonical(ipSpan, out int end, notImplicitFile: true);
 180329                return address != IPv4AddressHelper.Invalid && end == ipSpan.Length;
 30            }
 419231        }
 32
 33        internal static unsafe IPAddress? Parse<TChar>(ReadOnlySpan<TChar> ipSpan, bool tryParse)
 34            where TChar : unmanaged, IBinaryInteger<TChar>
 353135        {
 353136            Debug.Assert(typeof(TChar) == typeof(byte) || typeof(TChar) == typeof(char));
 37
 353138            if (ipSpan.Contains(TChar.CreateTruncating(':')))
 185139            {
 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.
 185142                Span<ushort> numbers = stackalloc ushort[IPAddressParserStatics.IPv6AddressShorts];
 185143                numbers.Clear();
 185144                if (TryParseIPv6(ipSpan, numbers, IPAddressParserStatics.IPv6AddressShorts, out uint scope))
 107645                {
 107646                    return new IPAddress(numbers, scope);
 47                }
 77548            }
 168049            else if (TryParseIpv4(ipSpan, out long address))
 24650            {
 24651                return new IPAddress(address);
 52            }
 53
 220954            if (tryParse)
 220955            {
 220956                return null;
 57            }
 58
 059            throw new FormatException(SR.dns_bad_ip_address, new SocketException(SocketError.InvalidArgument));
 353160        }
 61
 62        private static bool TryParseIpv4<TChar>(ReadOnlySpan<TChar> ipSpan, out long address)
 63            where TChar : unmanaged, IBinaryInteger<TChar>
 168064        {
 168065            long tmpAddr = IPv4AddressHelper.ParseNonCanonical(ipSpan, out int end, notImplicitFile: true);
 66
 168067            if (tmpAddr != IPv4AddressHelper.Invalid && end == ipSpan.Length)
 24668            {
 69                // IPv4AddressHelper.ParseNonCanonical returns the bytes in host order.
 70                // Convert to network order and return success.
 24671                address = (uint)IPAddress.HostToNetworkOrder(unchecked((int)tmpAddr));
 24672                return true;
 73            }
 74
 75            // Failed to parse the address.
 143476            address = 0;
 143477            return false;
 168078        }
 79
 80        private static bool TryParseIPv6<TChar>(ReadOnlySpan<TChar> ipSpan, Span<ushort> numbers, int numbersLength, out
 81            where TChar : unmanaged, IBinaryInteger<TChar>
 185182        {
 185183            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 185184            Debug.Assert(numbersLength >= IPAddressParserStatics.IPv6AddressShorts);
 85
 185186            if (!IPv6AddressHelper.IsValidStrict(ipSpan))
 77587            {
 77588                scope = 0;
 77589                return false;
 90            }
 91
 107692            IPv6AddressHelper.Parse(ipSpan, numbers, out ReadOnlySpan<TChar> scopeIdSpan);
 93
 107694            if (scopeIdSpan.Length > 1)
 30295            {
 96                bool parsedNumericScope;
 30297                scopeIdSpan = scopeIdSpan.Slice(1);
 98
 99                // scopeId is a numeric value
 302100                if (typeof(TChar) == typeof(byte))
 151101                {
 151102                    ReadOnlySpan<byte> castScopeIdSpan = MemoryMarshal.Cast<TChar, byte>(scopeIdSpan);
 103
 151104                    parsedNumericScope = uint.TryParse(castScopeIdSpan, NumberStyles.None, CultureInfo.InvariantCulture,
 151105                }
 106                else
 151107                {
 151108                    ReadOnlySpan<char> castScopeIdSpan = MemoryMarshal.Cast<TChar, char>(scopeIdSpan);
 109
 151110                    parsedNumericScope = uint.TryParse(castScopeIdSpan, NumberStyles.None, CultureInfo.InvariantCulture,
 151111                }
 112
 302113                if (parsedNumericScope)
 34114                {
 34115                    return true;
 116                }
 117                else
 268118                {
 268119                    uint interfaceIndex = InterfaceInfoPal.InterfaceNameToIndex(scopeIdSpan);
 120
 268121                    if (interfaceIndex > 0)
 2122                    {
 2123                        scope = interfaceIndex;
 2124                        return true; // scopeId is a known interface name
 125                    }
 266126                }
 127
 128                // scopeId is an unknown interface name
 266129            }
 130
 131            // scopeId is not presented
 1040132            scope = 0;
 1040133            return true;
 1851134        }
 135
 136        internal static int FormatIPv4Address<TChar>(uint address, Span<TChar> addressString)
 137            where TChar : unmanaged, IBinaryInteger<TChar>
 208138        {
 208139            address = (uint)IPAddress.NetworkToHostOrder(unchecked((int)address));
 140
 208141            int pos = FormatByte(address >> 24, addressString);
 208142            addressString[pos++] = TChar.CreateTruncating('.');
 208143            pos += FormatByte(address >> 16, addressString.Slice(pos));
 208144            addressString[pos++] = TChar.CreateTruncating('.');
 208145            pos += FormatByte(address >> 8, addressString.Slice(pos));
 208146            addressString[pos++] = TChar.CreateTruncating('.');
 208147            pos += FormatByte(address, addressString.Slice(pos));
 148
 208149            return pos;
 150
 151            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 152            static int FormatByte(uint number, Span<TChar> addressString)
 832153            {
 832154                number &= 0xFF;
 155
 832156                if (number >= 10)
 198157                {
 158                    uint hundreds, tens;
 198159                    if (number >= 100)
 60160                    {
 60161                        (uint hundredsAndTens, number) = Math.DivRem(number, 10);
 60162                        (hundreds, tens) = Math.DivRem(hundredsAndTens, 10);
 163
 60164                        addressString[2] = TChar.CreateTruncating('0' + number);
 60165                        addressString[1] = TChar.CreateTruncating('0' + tens);
 60166                        addressString[0] = TChar.CreateTruncating('0' + hundreds);
 60167                        return 3;
 168                    }
 169
 138170                    (tens, number) = Math.DivRem(number, 10);
 138171                    addressString[1] = TChar.CreateTruncating('0' + number);
 138172                    addressString[0] = TChar.CreateTruncating('0' + tens);
 138173                    return 2;
 174                }
 175
 634176                addressString[0] = TChar.CreateTruncating('0' + number);
 634177                return 1;
 832178            }
 208179        }
 180
 181        internal static int FormatIPv6Address<TChar>(ushort[] address, uint scopeId, Span<TChar> destination)
 182            where TChar : unmanaged, IBinaryInteger<TChar>
 538183        {
 538184            Debug.Assert(typeof(TChar) == typeof(byte) || typeof(TChar) == typeof(char));
 538185            int pos = 0;
 186
 538187            if (IPv6AddressHelper.ShouldHaveIpv4Embedded(address))
 85188            {
 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.
 85191                AppendSections(address.AsSpan(0, 6), destination, ref pos);
 85192                if (destination[pos - 1] != TChar.CreateTruncating(':'))
 0193                {
 0194                    destination[pos++] = TChar.CreateTruncating(':');
 0195                }
 196
 85197                pos += FormatIPv4Address(ExtractIPv4Address(address), destination.Slice(pos));
 85198            }
 199            else
 453200            {
 201                // No IPv4 address.  Output all 8 sections as part of the IPv6 address
 202                // with normal formatting rules.
 453203                AppendSections(address.AsSpan(0, 8), destination, ref pos);
 453204            }
 205
 206            // If there's a scope ID, append it.
 538207            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
 538220            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)
 538226            {
 227                // Find the longest sequence of zeros to be combined into a "::"
 538228                (int zeroStart, int zeroEnd) = IPv6AddressHelper.FindCompressionRange(address);
 538229                bool needsColon = false;
 230
 231                // Handle a zero sequence if there is one
 538232                if (zeroStart >= 0)
 440233                {
 234                    // Output all of the numbers before the zero sequence
 1726235                    for (int i = 0; i < zeroStart; i++)
 423236                    {
 423237                        if (needsColon)
 207238                        {
 207239                            destination[offset++] = TChar.CreateTruncating(':');
 207240                        }
 423241                        needsColon = true;
 423242                        AppendHex(address[i], destination, ref offset);
 423243                    }
 244
 245                    // Output the zero sequence if there is one
 440246                    destination[offset++] = TChar.CreateTruncating(':');
 440247                    destination[offset++] = TChar.CreateTruncating(':');
 440248                    needsColon = false;
 440249                }
 250
 251                // Output everything after the zero sequence
 3386252                for (int i = zeroEnd; i < address.Length; i++)
 1155253                {
 1155254                    if (needsColon)
 873255                    {
 873256                        destination[offset++] = TChar.CreateTruncating(':');
 873257                    }
 1155258                    needsColon = true;
 1155259                    AppendHex(address[i], destination, ref offset);
 1155260                }
 538261            }
 262
 263            // Appends a number as hexadecimal (without the leading "0x")
 264            static void AppendHex(ushort value, Span<TChar> destination, ref int offset)
 1578265            {
 1578266                if ((value & 0xFFF0) != 0)
 854267                {
 854268                    if ((value & 0xFF00) != 0)
 531269                    {
 531270                        if ((value & 0xF000) != 0)
 236271                        {
 236272                            destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value >> 12));
 236273                        }
 274
 531275                        destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value >> 8));
 531276                    }
 277
 854278                    destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value >> 4));
 854279                }
 280
 1578281                destination[offset++] = TChar.CreateTruncating(HexConverter.ToCharLower(value));
 1578282            }
 538283        }
 284
 285        /// <summary>Extracts the IPv4 address from the end of the IPv6 address byte array.</summary>
 286        private static uint ExtractIPv4Address(ushort[] address)
 85287        {
 85288            uint ipv4address = (uint)address[6] << 16 | address[7];
 85289            return (uint)IPAddress.HostToNetworkOrder(unchecked((int)ipv4address));
 85290        }
 291    }
 292}
 293