< Summary

Line coverage
93%
Covered lines: 227
Uncovered lines: 15
Coverable lines: 242
Total lines: 459
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)

https://raw.githubusercontent.com/dotnet/runtime/811a7eabb75c42db53440e8ba3f60c07511cfd1f/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> 
 53818        {
 161419            int longestSequenceLength = 0, longestSequenceStart = -1, currentSequenceLength = 0;
 20
 934421            for (int i = 0; i < numbers.Length; i++)
 413422            {
 413423                if (numbers[i] == 0)
 264524                {
 264525                    currentSequenceLength++;
 264526                    if (currentSequenceLength > longestSequenceLength)
 259527                    {
 259528                        longestSequenceLength = currentSequenceLength;
 259529                        longestSequenceStart = i - currentSequenceLength + 1;
 259530                    }
 264531                }
 32                else
 148933                {
 148934                    currentSequenceLength = 0;
 148935                }
 413436            }
 37
 53838            return longestSequenceLength > 1 ?
 53839                (longestSequenceStart, longestSequenceStart + longestSequenceLength) :
 53840                (-1, 0);
 53841        }
 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)
 53846        {
 47            // 0:0 : 0:0 : x:x : x.x.x.x
 53848            if (numbers[0] == 0 && numbers[1] == 0 && numbers[2] == 0 && numbers[3] == 0 && numbers[6] != 0)
 9349            {
 50                // RFC 5952 Section 5 - 0:0 : 0:0 : 0:[0 | FFFF] : x.x.x.x
 9351                if (numbers[4] == 0 && (numbers[5] == 0 || numbers[5] == 0xFFFF))
 8552                {
 8553                    return true;
 54                }
 55                // SIIT - 0:0 : 0:0 : FFFF:0 : x.x.x.x
 856                else if (numbers[4] == 0xFFFF && numbers[5] == 0)
 057                {
 058                    return true;
 59                }
 860            }
 61
 62            // ISATAP
 45363            return numbers[4] == 0 && numbers[5] == 0x5EFE;
 53864        }
 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>
 4240100        {
 4240101            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 102
 103            // Number of components in this IPv6 address
 4240104            int sequenceCount = 0;
 105            // Length of the component currently being constructed
 4240106            int sequenceLength = 0;
 4240107            bool haveCompressor = false;
 4240108            bool haveIPv4Address = false;
 4240109            bool expectingNumber = true;
 110            // Start position of the previous component
 4240111            int lastSequence = 1;
 4240112            int start = 0;
 4240113            int end = name.Length;
 114
 4240115            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 (']').
 4240119            if (start < end && name[start] == TChar.CreateTruncating('['))
 804120            {
 804121                start++;
 804122                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.
 804127                Debug.Assert(start < end);
 804128            }
 129
 130            // Starting with a colon character is only valid if another colon follows.
 4240131            if (name[start] == TChar.CreateTruncating(':') && (start + 1 >= end || name[start + 1] != TChar.CreateTrunca
 26132            {
 26133                return false;
 134            }
 135
 136            int i;
 68322137            for (i = start; i < end; ++i)
 31297138            {
 31297139                int currentCh = IPv4AddressHelper.ToUShort(name[i]);
 140
 31297141                if (HexConverter.IsHexChar(currentCh))
 18322142                {
 18322143                    ++sequenceLength;
 18322144                    expectingNumber = false;
 18322145                }
 146                else
 12975147                {
 12975148                    if (sequenceLength > 4)
 16149                    {
 16150                        return false;
 151                    }
 12959152                    if (sequenceLength != 0)
 7957153                    {
 7957154                        ++sequenceCount;
 7957155                        lastSequence = i - sequenceLength;
 7957156                        sequenceLength = 0;
 7957157                    }
 158
 12959159                    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 ('/').
 19077165                            while (i + 1 < end)
 18497166                            {
 18497167                                i++;
 18497168                                if (name[i] == TChar.CreateTruncating(']'))
 426169                                {
 426170                                    goto case ']';
 171                                }
 18071172                                else if (name[i] == TChar.CreateTruncating('/'))
 66173                                {
 66174                                    goto case '/';
 175                                }
 18005176                            }
 580177                            break;
 178
 179                        case ']':
 776180                            if (!needsClosingBracket)
 12181                            {
 12182                                return false;
 183                            }
 764184                            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.
 764188                            if (i + 1 < end && name[i + 1] != TChar.CreateTruncating(':'))
 6189                            {
 6190                                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.
 758195                            if (i + 3 < end && name[i + 2] == TChar.CreateTruncating('0') && name[i + 3] == TChar.Create
 210196                            {
 210197                                i += 4;
 4578198                                for (; i < end; i++)
 2276199                                {
 2276200                                    int ch = IPv4AddressHelper.ToUShort(name[i]);
 201
 2276202                                    if (!HexConverter.IsHexChar(ch))
 92203                                    {
 92204                                        return false;
 205                                    }
 2184206                                }
 118207                            }
 208                            else
 548209                            {
 548210                                i += 2;
 9628211                                for (; i < end; i++)
 4718212                                {
 4718213                                    if (!char.IsAsciiDigit((char)IPv4AddressHelper.ToUShort(name[i])))
 178214                                    {
 178215                                        return false;
 216                                    }
 4540217                                }
 370218                            }
 488219                            continue;
 220
 221                        case ':':
 222                            // If the next character after a colon is another colon, the address contains a compressor (
 10052223                            if ((i > 0) && (name[i - 1] == TChar.CreateTruncating(':')))
 2348224                            {
 2348225                                if (haveCompressor)
 4226                                {
 227                                    // can only have one per IPv6 address
 4228                                    return false;
 229                                }
 2344230                                haveCompressor = true;
 2344231                                expectingNumber = false;
 2344232                            }
 233                            else
 7704234                            {
 7704235                                expectingNumber = true;
 7704236                            }
 10048237                            break;
 238
 239                        case '/':
 240                            // A prefix in an IPv6 address is invalid.
 74241                            return false;
 242
 243                        case '.':
 1291244                            if (haveIPv4Address)
 0245                            {
 0246                                return false;
 247                            }
 248
 1291249                            if (!IPv4AddressHelper.IsValid(name.Slice(lastSequence, end - lastSequence), out int seqEnd,
 782250                            {
 782251                                return false;
 252                            }
 509253                            i = lastSequence + seqEnd;
 254
 255                            // An IPv4 address takes 2 slots in an IPv6 address. One was just counted meeting the '.'
 509256                            ++sequenceCount;
 509257                            lastSequence = i - sequenceLength;
 509258                            haveIPv4Address = true;
 509259                            --i;            // it will be incremented back on the next loop
 509260                            break;
 261
 262                        default:
 186263                            return false;
 264                    }
 11137265                    sequenceLength = 0;
 11137266                }
 29459267            }
 268
 2864269            if (sequenceLength != 0)
 1152270            {
 1152271                if (sequenceLength > 4)
 6272                {
 6273                    return false;
 274                }
 275
 1146276                ++sequenceCount;
 1146277            }
 278
 279            // These sequence counts are -1 because it is implied in end-of-sequence.
 280
 281            const int ExpectedSequenceCount = 8;
 2858282            return
 2858283                !expectingNumber &&
 2858284                (haveCompressor ? (sequenceCount < ExpectedSequenceCount) : (sequenceCount == ExpectedSequenceCount)) &&
 2858285                !needsClosingBracket;
 4240286        }
 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>
 1076315        {
 1076316            Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
 317
 1076318            int number = 0;
 319            int currentCh;
 1076320            int index = 0;
 1076321            int compressorIndex = -1;
 1076322            bool numberIsValid = true;
 323
 1076324            scopeId = ReadOnlySpan<TChar>.Empty;
 325
 326            // Skip the start '[' character, if present. Stop parsing at the end IPv6 address terminator (']').
 11804327            for (int i = (address[0] == TChar.CreateTruncating('[') ? 1 : 0); i < address.Length && address[i] != TChar.
 9652328            {
 9652329                currentCh = IPv4AddressHelper.ToUShort(address[i]);
 330
 9652331                switch (currentCh)
 332                {
 333                    case '%':
 382334                        if (numberIsValid)
 332335                        {
 332336                            numbers[index++] = (ushort)number;
 332337                            numberIsValid = false;
 332338                        }
 339
 340                        // The scope follows a '%' and terminates at the natural end of the address, the address termina
 382341                        int scopeStart = i;
 342
 18030343                        for (++i; i < address.Length && address[i] != TChar.CreateTruncating(']') && address[i] != TChar
 8633344                        {
 8633345                        }
 382346                        scopeId = address.Slice(scopeStart, i - scopeStart);
 347
 348                        // Ignore the prefix (if any.)
 382349                        for (; i < address.Length && address[i] != TChar.CreateTruncating(']'); ++i)
 0350                        {
 0351                        }
 382352                        break;
 353
 354                    case ':':
 3050355                        numbers[index++] = (ushort)number;
 3050356                        number = 0;
 357                        // Two sequential colons form a compressor ('::').
 3050358                        ++i;
 3050359                        if (address[i] == TChar.CreateTruncating(':'))
 942360                        {
 942361                            compressorIndex = index;
 942362                            ++i;
 942363                        }
 2108364                        else if ((compressorIndex < 0) && (index < 6))
 1224365                        {
 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.
 1224369                            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
 6444374                        for (int j = i; j < address.Length &&
 4618375                                        (address[j] != TChar.CreateTruncating(']')) &&
 4618376                                        (address[j] != TChar.CreateTruncating(':')) &&
 4618377                                        (address[j] != TChar.CreateTruncating('%')) &&
 4618378                                        (address[j] != TChar.CreateTruncating('/')) &&
 7410379                                        (j < i + 4); ++j)
 2994380                        {
 381
 2994382                            if (address[j] == TChar.CreateTruncating('.'))
 202383                            {
 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 ']').
 1560390                                while (j < address.Length && (address[j] != TChar.CreateTruncating(']')) && (address[j] 
 1358391                                {
 1358392                                    ++j;
 1358393                                }
 202394                                int ipv4Address = IPv4AddressHelper.ParseHostNumber(address, i, j);
 395
 202396                                numbers[index++] = (ushort)(ipv4Address >> 16);
 202397                                numbers[index++] = (ushort)(ipv4Address & 0xFFFF);
 202398                                i = j;
 399
 400                                // Set this to avoid adding another number to
 401                                // the array if there's a prefix
 202402                                number = 0;
 202403                                numberIsValid = false;
 202404                                break;
 405                            }
 2792406                        }
 1826407                        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:
 6220423                        int characterValue = HexConverter.FromChar(currentCh);
 424
 6220425                        number = number * IPv6AddressHelper.Hex + characterValue;
 6220426                        i++;
 6220427                        break;
 428                }
 9652429            }
 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
 1076433            if (numberIsValid)
 542434            {
 542435                numbers[index++] = (ushort)number;
 542436            }
 437
 438            // If we had a compressor sequence ("::") then we need to expand the
 439            // numbers array.
 1076440            if (compressorIndex > 0)
 942441            {
 942442                int toIndex = NumberOfLabels - 1;
 942443                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.
 942447                if (fromIndex != toIndex)
 890448                {
 4752449                    for (int i = index - compressorIndex; i > 0; --i)
 1486450                    {
 1486451                        numbers[toIndex--] = numbers[fromIndex];
 1486452                        numbers[fromIndex--] = 0;
 1486453                    }
 890454                }
 942455            }
 1076456        }
 457    }
 458}
 459