parseIPv6Address static method
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:
::1FEDC:BA98:7654:3210:FEDC:BA98:7654:32103ffe:2a00:100:7031::1::FFFF:129.144.52.382010: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;
}