parseIPv6Address static method

List<int> parseIPv6Address(
  1. String host, [
  2. int start = 0,
  3. int? end
])

Parses the host as an IP version 6 (IPv6) address.

Returns the address as a list of 16 bytes in network byte order (big endian).

Throws a FormatException if host is not a valid IPv6 address representation.

Acts on the substring from start to end. If end is omitted, it defaults to the end of the string.

Some examples of IPv6 addresses:

  • ::1
  • FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
  • 3ffe:2a00:100:7031::1
  • ::FFFF:129.144.52.38
  • 2010:836B:4179::836B:4179

The grammar for IPv6 addresses are:

IPv6address ::=                           (h16 ":"){6} ls32
              |                      "::" (h16 ":"){5} ls32
              | (               h16) "::" (h16 ":"){4} ls32
              | ((h16 ":"){0,1} h16) "::" (h16 ":"){3} ls32
              | ((h16 ":"){0,2} h16) "::" (h16 ":"){2} ls32
              | ((h16 ":"){0,3} h16) "::"  h16 ":"     ls32
              | ((h16 ":"){0,4} h16) "::"              ls32
              | ((h16 ":"){0,5} h16) "::"              h16
              | ((h16 ":"){0,6} h16) "::"
ls32        ::= (h16 ":" h16) | IPv4address
                ;; least-significant 32 bits of address
h16         ::= HEXDIG{1,4}
                ;; 16 bits of address represented in hexadecimal

That is

  • eight 1-to-4-digit hexadecimal numerals separated by :, or
  • one to seven such :-separated numerals, with either one pair is separated by ::, or a leading or trailing ::.
  • either of the above with a trailing two :-separated numerals replaced by an IPv4 address.

An IPv6 address with a zone ID (from RFC 6874) is an IPv6 address followed by %25 (an escaped %) and valid zone characters.

IPv6addrz ::= IPv6address "%25" ZoneID
ZoneID    ::= (unreserved | pct-encoded)+
```.

Implementation

static List<int> parseIPv6Address(String host, [int start = 0, int? end]) {
  end ??= RangeError.checkValidRange(start, end, host.length);
  // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, separated
  // by `:`'s, with the following exceptions:
  //
  //  - One (and only one) wildcard (`::`) may be present, representing a fill
  //    of 0's. The IPv6 `::` is thus 16 bytes of `0`.
  //  - The last two parts may be replaced by an IPv4 "dotted-quad" address.

  // Helper function for reporting a badly formatted IPv6 address.
  Never error(String msg, int? position) {
    throw FormatException('Illegal IPv6 address, $msg', host, position);
  }

  if (end - start < 2) error('address is too short', null);
  Uint8List result = Uint8List(16);
  // Set if seeing a ".", suggesting that there is an IPv4 address.
  int partStart = start;
  int wildcardAt = -1;
  int partCount = 0;
  int hexValue = 0;
  bool decValue = true;
  int cursor = start;
  // Handle leading colons eagerly to make the loop simpler.
  // After this, any colon encountered will be after another colon or
  // a hex part.
  if (host.codeUnitAt(cursor) == _COLON) {
    if (host.codeUnitAt(cursor + 1) == _COLON) {
      wildcardAt = 0;
      partCount = 1;
      partStart = cursor += 2;
    } else {
      error('invalid start colon', cursor);
    }
  }
  // Parse all parts, except a potential last one.
  while (true) {
    var char = cursor >= end ? 0 : host.codeUnitAt(cursor);
    handleDigit:
    {
      int hexDigit;
      if (char ^ _DIGIT_0 case var digit when digit <= 9) {
        hexDigit = digit;
      } else if (char | 0x20 case var letter
          when letter >= _LOWER_CASE_A && letter <= _LOWER_CASE_F) {
        hexDigit = letter - (_LOWER_CASE_A - 10);
        decValue = false;
      } else {
        break handleDigit; // Not a digit.
      }
      if (cursor < partStart + 4) {
        hexValue = hexValue * 16 + hexDigit;
        cursor++;
        continue;
      }
      error('an IPv6 part can contain a maximum of 4 hex digits', partStart);
    }
    if (cursor > partStart) {
      // Non-empty part, value in hexValue.
      if (char == _DOT) {
        if (decValue) {
          if (partCount <= 6) {
            _parseIPv4Address(host, partStart, end, result, partCount * 2);
            cursor = end; // Throws if IPv4 does not extend to end.
            partCount += 2;
            break; // Must be last.
          }
          error('an address must contain at most 8 parts', partStart);
        }
        break; // Any other dot is just an invalid character.
      }
      // Non-empty hex part.
      result[partCount * 2] = hexValue >> 8;
      result[partCount * 2 + 1] = hexValue & 0xFF;
      partCount++;
      if (char == _COLON) {
        if (partCount < 8) {
          // Start new part.
          hexValue = 0;
          decValue = true;
          cursor++;
          partStart = cursor;
          continue;
        }
        error('an address must contain at most 8 parts', cursor);
      }
      break; // Unexpected character or the end sentinel.
    }
    // Empty part, after a colon.
    if (char == _COLON) {
      // Colon after empty part must be after colon.
      // (Leading colons were checked before loop.)
      if (wildcardAt < 0) {
        wildcardAt = partCount;
        partCount++; // A wildcard fills at least one part.
        partStart = ++cursor;
        continue;
      }
      error('only one wildcard `::` is allowed', cursor);
    }
    // Missing hex digits after colon.
    // OK to end right after wildcard, not after another colon.
    if (wildcardAt != partCount - 1) {
      // Missing part after a single `:`.
      // Error even at end.
      error('missing part', cursor);
    }
    break; // Error if not at end.
  }
  if (cursor < end) error('invalid character', cursor);
  if (partCount < 8) {
    if (wildcardAt < 0) {
      error(
        'an address without a wildcard must contain exactly 8 parts',
        end,
      );
    }
    // Move everything after the wildcard to the end of the buffer, and fill
    // the wildcard gap with zeros.
    var partAfterWildcard = wildcardAt + 1;
    int partsAfterWildcard = partCount - partAfterWildcard;
    if (partsAfterWildcard > 0) {
      var positionAfterWildcard = partAfterWildcard * 2;
      var bytesAfterWildcard = partsAfterWildcard * 2;
      var newPositionAfterWildcard = result.length - bytesAfterWildcard;
      result.setRange(
        newPositionAfterWildcard,
        result.length,
        result,
        positionAfterWildcard,
      );
      result.fillRange(positionAfterWildcard, newPositionAfterWildcard, 0);
    }
  }
  return result;
}