< Summary

Information
Class: System.Net.IPv6AddressHelper
Assembly: System.Net.Primitives
File(s): D:\runner\runtime\src\libraries\Common\src\System\Net\IPv6AddressHelper.Common.cs
Line coverage
93%
Covered lines: 227
Uncovered lines: 15
Coverable lines: 242
Total lines: 458
Line coverage: 93.8%
Branch coverage
93%
Covered branches: 165
Total branches: 176
Branch coverage: 93.7%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
FindCompressionRange(...)100%88100%
ShouldHaveIpv4Embedded(...)90.9%222283.33%
IsValidStrict(...)97.43%787898.31%
Parse(...)89.7%686887.77%

File(s)

D:\runner\runtime\src\libraries\Common\src\System\Net\IPv6AddressHelper.Common.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.Numerics;
 6
 7namespace System.Net
 8{
 9    internal static partial class IPv6AddressHelper
 10    {
 11        private const int Hex = 16;
 12        private const int NumberOfLabels = 8;
 13
 14        // RFC 5952 Section 4.2.3
 15        // Longest consecutive sequence of zero segments, minimum 2.
 16        // On equal, first sequence wins. <-1, -1> for no compression.
 17        internal static (int longestSequenceStart, int longestSequenceLength) FindCompressionRange(ReadOnlySpan<ushort> 
 47018        {
 141019            int longestSequenceLength = 0, longestSequenceStart = -1, currentSequenceLength = 0;
 20
 825621            for (int i = 0; i < numbers.Length; i++)
 365822            {
 365823                if (numbers[i] == 0)
 237524                {
 237525                    currentSequenceLength++;
 237526                    if (currentSequenceLength > longestSequenceLength)
 222827                    {
 222828                        longestSequenceLength = currentSequenceLength;
 222829                        longestSequenceStart = i - currentSequenceLength + 1;
 222830                    }
 237531                }
 32                else
 128333                {
 128334                    currentSequenceLength = 0;
 128335                }
 365836            }
 37
 47038            return longestSequenceLength > 1 ?
 47039                (longestSequenceStart, longestSequenceStart + longestSequenceLength) :
 47040                (-1, 0);
 47041        }
 42
 43        // Returns true if the IPv6 address should be formatted with an embedded IPv4 address:
 44        // ::192.168.1.1
 45        internal static bool ShouldHaveIpv4Embedded(ReadOnlySpan<ushort> numbers)
 47046        {
 47            // 0:0 : 0:0 : x:x : x.x.x.x
 47048            if (numbers[0] == 0 && numbers[1] == 0 && numbers[2] == 0 && numbers[3] == 0 && numbers[6] != 0)
 7349            {
 50                // RFC 5952 Section 5 - 0:0 : 0:0 : 0:[0 | FFFF] : x.x.x.x
 7351                if (numbers[4] == 0 && (numbers[5] == 0 || numbers[5] == 0xFFFF))
 5152                {
 5153                    return true;
 54                }
 55                // SIIT - 0:0 : 0:0 : FFFF:0 : x.x.x.x
 2256                else if (numbers[4] == 0xFFFF && numbers[5] == 0)
 057                {
 058                    return true;
 59                }
 2260            }
 61
 62            // ISATAP
 41963            return numbers[4] == 0 && numbers[5] == 0x5EFE;
 47064        }
 65
 66        //
 67        // IsValidStrict
 68        //
 69        //  Determine whether a name is a valid IPv6 address. Rules are:
 70        //
 71        //   *  8 groups of 16-bit hex numbers, separated by ':'
 72        //   *  a *single* run of zeros can be compressed using the symbol '::'
 73        //   *  an optional string of a ScopeID delimited by '%'
 74        //   *  the last 32 bits in an address can be represented as an IPv4 address
 75        //
 76        //  Difference between IsValid() and IsValidStrict() is that IsValid() expects part of the string to
 77        //  be ipv6 address where as IsValidStrict() expects strict ipv6 address.
 78        //
 79        // Inputs:
 80        //  <argument>  name
 81        //      IPv6 address in string format
 82        //
 83        // Outputs:
 84        //  Nothing
 85        //
 86        // Assumes:
 87        //  the correct name is terminated by  ']' character
 88        //
 89        // Returns:
 90        //  true if <name> is IPv6  address, else false
 91        //
 92        // Throws:
 93        //  Nothing
 94        //
 95
 96        //  Remarks: MUST NOT be used unless all input indexes are verified and trusted.
 97        //           start must be next to '[' position, or error is reported
 98        internal static bool IsValidStrict<TChar>(ReadOnlySpan<TChar> name)
 99            where TChar : unmanaged, IBinaryInteger<TChar>
 3456100        {
 3456101            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 102
 103            // Number of components in this IPv6 address
 3456104            int sequenceCount = 0;
 105            // Length of the component currently being constructed
 3456106            int sequenceLength = 0;
 3456107            bool haveCompressor = false;
 3456108            bool haveIPv4Address = false;
 3456109            bool expectingNumber = true;
 110            // Start position of the previous component
 3456111            int lastSequence = 1;
 3456112            int start = 0;
 3456113            int end = name.Length;
 114
 3456115            bool needsClosingBracket = false;
 116
 117            // An IPv6 address may begin with a start character ('['). If it does, it must end with an end
 118            // character (']').
 3456119            if (start < end && name[start] == TChar.CreateTruncating('['))
 506120            {
 506121                start++;
 506122                needsClosingBracket = true;
 123
 124                // IsValidStrict() is only called if there is a ':' in the name string, i.e.
 125                // it is a possible IPv6 address. So, if the string starts with a '[' and
 126                // the pointer is advanced here there are still more characters to parse.
 506127                Debug.Assert(start < end);
 506128            }
 129
 130            // Starting with a colon character is only valid if another colon follows.
 3456131            if (name[start] == TChar.CreateTruncating(':') && (start + 1 >= end || name[start + 1] != TChar.CreateTrunca
 20132            {
 20133                return false;
 134            }
 135
 136            int i;
 58116137            for (i = start; i < end; ++i)
 26550138            {
 26550139                int currentCh = IPv4AddressHelper.ToUShort(name[i]);
 140
 26550141                if (HexConverter.IsHexChar(currentCh))
 15183142                {
 15183143                    ++sequenceLength;
 15183144                    expectingNumber = false;
 15183145                }
 146                else
 11367147                {
 11367148                    if (sequenceLength > 4)
 10149                    {
 10150                        return false;
 151                    }
 11357152                    if (sequenceLength != 0)
 7099153                    {
 7099154                        ++sequenceCount;
 7099155                        lastSequence = i - sequenceLength;
 7099156                        sequenceLength = 0;
 7099157                    }
 158
 11357159                    switch (currentCh)
 160                    {
 161                        case '%':
 162                            // An IPv6 address is separated from its scope by a '%' character. The scope
 163                            // is terminated by the natural end of the address, the address end character (']')
 164                            // or the start of the prefix ('/').
 11974165                            while (i + 1 < end)
 11602166                            {
 11602167                                i++;
 11602168                                if (name[i] == TChar.CreateTruncating(']'))
 144169                                {
 144170                                    goto case ']';
 171                                }
 11458172                                else if (name[i] == TChar.CreateTruncating('/'))
 94173                                {
 94174                                    goto case '/';
 175                                }
 11364176                            }
 372177                            break;
 178
 179                        case ']':
 518180                            if (!needsClosingBracket)
 20181                            {
 20182                                return false;
 183                            }
 498184                            needsClosingBracket = false;
 185
 186                            // If there's more after the closing bracket, it must be a port.
 187                            // We don't use the port, but we still validate it.
 498188                            if (i + 1 < end && name[i + 1] != TChar.CreateTruncating(':'))
 20189                            {
 20190                                return false;
 191                            }
 192
 193                            // If there is a port, it must either be a hexadecimal or decimal number.
 194                            // If the next two characters are '0x' then it's a hexadecimal number. Skip the prefix.
 478195                            if (i + 3 < end && name[i + 2] == TChar.CreateTruncating('0') && name[i + 3] == TChar.Create
 162196                            {
 162197                                i += 4;
 5534198                                for (; i < end; i++)
 2724199                                {
 2724200                                    int ch = IPv4AddressHelper.ToUShort(name[i]);
 201
 2724202                                    if (!HexConverter.IsHexChar(ch))
 38203                                    {
 38204                                        return false;
 205                                    }
 2686206                                }
 124207                            }
 208                            else
 316209                            {
 316210                                i += 2;
 4424211                                for (; i < end; i++)
 2170212                                {
 2170213                                    if (!char.IsAsciiDigit((char)IPv4AddressHelper.ToUShort(name[i])))
 116214                                    {
 116215                                        return false;
 216                                    }
 2054217                                }
 200218                            }
 324219                            continue;
 220
 221                        case ':':
 222                            // If the next character after a colon is another colon, the address contains a compressor (
 9534223                            if ((i > 0) && (name[i - 1] == TChar.CreateTruncating(':')))
 2158224                            {
 2158225                                if (haveCompressor)
 2226                                {
 227                                    // can only have one per IPv6 address
 2228                                    return false;
 229                                }
 2156230                                haveCompressor = true;
 2156231                                expectingNumber = false;
 2156232                            }
 233                            else
 7376234                            {
 7376235                                expectingNumber = true;
 7376236                            }
 9532237                            break;
 238
 239                        case '/':
 240                            // A prefix in an IPv6 address is invalid.
 100241                            return false;
 242
 243                        case '.':
 741244                            if (haveIPv4Address)
 0245                            {
 0246                                return false;
 247                            }
 248
 741249                            if (!IPv4AddressHelper.IsValid(name.Slice(lastSequence, end - lastSequence), out int seqEnd,
 530250                            {
 530251                                return false;
 252                            }
 211253                            i = lastSequence + seqEnd;
 254
 255                            // An IPv4 address takes 2 slots in an IPv6 address. One was just counted meeting the '.'
 211256                            ++sequenceCount;
 211257                            lastSequence = i - sequenceLength;
 211258                            haveIPv4Address = true;
 211259                            --i;            // it will be incremented back on the next loop
 211260                            break;
 261
 262                        default:
 92263                            return false;
 264                    }
 10115265                    sequenceLength = 0;
 10115266                }
 25298267            }
 268
 2508269            if (sequenceLength != 0)
 1313270            {
 1313271                if (sequenceLength > 4)
 12272                {
 12273                    return false;
 274                }
 275
 1301276                ++sequenceCount;
 1301277            }
 278
 279            // These sequence counts are -1 because it is implied in end-of-sequence.
 280
 281            const int ExpectedSequenceCount = 8;
 2496282            return
 2496283                !expectingNumber &&
 2496284                (haveCompressor ? (sequenceCount < ExpectedSequenceCount) : (sequenceCount == ExpectedSequenceCount)) &&
 2496285                !needsClosingBracket;
 3456286        }
 287
 288        //
 289        // Parse
 290        //
 291        //  Convert this IPv6 address into a sequence of 8 16-bit numbers
 292        //
 293        // Inputs:
 294        //  <member>    Name
 295        //      The validated IPv6 address
 296        //
 297        // Outputs:
 298        //  <member>    numbers
 299        //      Array filled in with the numbers in the IPv6 groups
 300        //
 301        //  <member>    scopeId
 302        //      Set to the text after the scope separator (%) if found
 303        //
 304        // Assumes:
 305        //  <Name> has been validated and contains only hex digits in groups of
 306        //  16-bit numbers, the characters ':', '/' and '%', and a possible IPv4
 307        //  address
 308        //
 309        // Throws:
 310        //  Nothing
 311        //
 312
 313        internal static void Parse<TChar>(ReadOnlySpan<TChar> address, scoped Span<ushort> numbers, out ReadOnlySpan<TCh
 314            where TChar : unmanaged, IBinaryInteger<TChar>
 940315        {
 940316            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 317
 940318            int number = 0;
 319            int currentCh;
 940320            int index = 0;
 940321            int compressorIndex = -1;
 940322            bool numberIsValid = true;
 323
 940324            scopeId = ReadOnlySpan<TChar>.Empty;
 325
 326            // Skip the start '[' character, if present. Stop parsing at the end IPv6 address terminator (']').
 10674327            for (int i = (address[0] == TChar.CreateTruncating('[') ? 1 : 0); i < address.Length && address[i] != TChar.
 8794328            {
 8794329                currentCh = IPv4AddressHelper.ToUShort(address[i]);
 330
 8794331                switch (currentCh)
 332                {
 333                    case '%':
 232334                        if (numberIsValid)
 184335                        {
 184336                            numbers[index++] = (ushort)number;
 184337                            numberIsValid = false;
 184338                        }
 339
 340                        // The scope follows a '%' and terminates at the natural end of the address, the address termina
 232341                        int scopeStart = i;
 342
 11644343                        for (++i; i < address.Length && address[i] != TChar.CreateTruncating(']') && address[i] != TChar
 5590344                        {
 5590345                        }
 232346                        scopeId = address.Slice(scopeStart, i - scopeStart);
 347
 348                        // Ignore the prefix (if any.)
 232349                        for (; i < address.Length && address[i] != TChar.CreateTruncating(']'); ++i)
 0350                        {
 0351                        }
 232352                        break;
 353
 354                    case ':':
 2930355                        numbers[index++] = (ushort)number;
 2930356                        number = 0;
 357                        // Two sequential colons form a compressor ('::').
 2930358                        ++i;
 2930359                        if (address[i] == TChar.CreateTruncating(':'))
 858360                        {
 858361                            compressorIndex = index;
 858362                            ++i;
 858363                        }
 2072364                        else if ((compressorIndex < 0) && (index < 6))
 1232365                        {
 366                            // No point checking for IPv4 address if we don't
 367                            // have a compressor or we haven't seen 6 16-bit
 368                            // numbers yet.
 1232369                            break;
 370                        }
 371
 372                        // Check to see if the upcoming number is really an IPv4
 373                        // address. If it is, convert it to 2 ushort numbers
 5704374                        for (int j = i; j < address.Length &&
 4006375                                        (address[j] != TChar.CreateTruncating(']')) &&
 4006376                                        (address[j] != TChar.CreateTruncating(':')) &&
 4006377                                        (address[j] != TChar.CreateTruncating('%')) &&
 4006378                                        (address[j] != TChar.CreateTruncating('/')) &&
 6314379                                        (j < i + 4); ++j)
 2386380                        {
 381
 2386382                            if (address[j] == TChar.CreateTruncating('.'))
 78383                            {
 384                                // We have an IPv4 address. Find the end of it:
 385                                // we know that since we have a valid IPv6
 386                                // address, the only things that will terminate
 387                                // the IPv4 address are the prefix delimiter '/'
 388                                // or the end-of-string (which we conveniently
 389                                // delimited with ']').
 688390                                while (j < address.Length && (address[j] != TChar.CreateTruncating(']')) && (address[j] 
 610391                                {
 610392                                    ++j;
 610393                                }
 78394                                int ipv4Address = IPv4AddressHelper.ParseHostNumber(address, i, j);
 395
 78396                                numbers[index++] = (ushort)(ipv4Address >> 16);
 78397                                numbers[index++] = (ushort)(ipv4Address & 0xFFFF);
 78398                                i = j;
 399
 400                                // Set this to avoid adding another number to
 401                                // the array if there's a prefix
 78402                                number = 0;
 78403                                numberIsValid = false;
 78404                                break;
 405                            }
 2308406                        }
 1698407                        break;
 408
 409                    case '/':
 0410                        if (numberIsValid)
 0411                        {
 0412                            numbers[index++] = (ushort)number;
 0413                            numberIsValid = false;
 0414                        }
 415
 0416                        for (++i; i < address.Length && address[i] != TChar.CreateTruncating(']'); i++)
 0417                        {
 0418                        }
 419
 0420                        break;
 421
 422                    default:
 5632423                        int characterValue = HexConverter.FromChar(currentCh);
 424
 5632425                        number = number * IPv6AddressHelper.Hex + characterValue;
 5632426                        i++;
 5632427                        break;
 428                }
 8794429            }
 430
 431            // Add number to the array if it's not the prefix length or part of
 432            // an IPv4 address that's already been handled
 940433            if (numberIsValid)
 678434            {
 678435                numbers[index++] = (ushort)number;
 678436            }
 437
 438            // If we had a compressor sequence ("::") then we need to expand the
 439            // numbers array.
 940440            if (compressorIndex > 0)
 858441            {
 858442                int toIndex = NumberOfLabels - 1;
 858443                int fromIndex = index - 1;
 444
 445                // If fromIndex and toIndex are the same, it means that "zero bits" are already in the correct place.
 446                // This happens for leading and trailing compression.
 858447                if (fromIndex != toIndex)
 822448                {
 4504449                    for (int i = index - compressorIndex; i > 0; --i)
 1430450                    {
 1430451                        numbers[toIndex--] = numbers[fromIndex];
 1430452                        numbers[fromIndex--] = 0;
 1430453                    }
 822454                }
 858455            }
 940456        }
 457    }
 458}