///|
/// `text` in RFC 5952's canonical form if it is an RFC 3986 `IPv6address`:
/// lower-case hex without leading zeros, the longest run of two or more zero
/// groups (the first, if tied) as `::`, and a trailing dotted quad kept
/// where the input had one (RFC 5952 §5). None if it is not an address.
fn ipv6_canonical(text : String) -> String? {
let halves = text.split("::").to_array()
guard halves.length() <= 2 else { return None }
let parsed = []
let mut dotted = false
for h, half in halves {
let groups = []
if half.length() > 0 {
let parts = half.split(":").to_array()
for p, part in parts {
let last = h == halves.length() - 1 && p == parts.length() - 1
if last && part.contains(".") {
guard ipv4_octets(part) is Some(octets) else { return None }
groups.push(octets[0] * 256 + octets[1])
groups.push(octets[2] * 256 + octets[3])
dotted = true
} else {
guard part.length() >= 1 &&
part.length() <= 4 &&
part.iter().all(c => c.is_ascii_hexdigit()) else {
return None
}
groups.push(
part.iter().fold(init=0, (value, c) => value * 16 + hex_value(c)),
)
}
}
}
parsed.push(groups)
}
let groups = if parsed.length() == 2 {
let missing = 8 - parsed[0].length() - parsed[1].length()
guard missing >= 1 else { return None }
[..parsed[0], ..Array::make(missing, 0), ..parsed[1]]
} else {
guard parsed[0].length() == 8 else { return None }
parsed[0]
}
Some(ipv6_text(groups, dotted~))
}
///|
/// Writes eight groups by RFC 5952 §4, the last two as a dotted quad when
/// `dotted`; the run compressed to `::` is then sought among the first six.
fn ipv6_text(groups : Array[Int32], dotted~ : Bool) -> String {
let hex_groups = if dotted { 6 } else { 8 }
let mut best_start = -1
let mut best_length = 1
let mut i = 0
while i < hex_groups {
if groups[i] != 0 {
i += 1
continue
}
let start = i
while i < hex_groups && groups[i] == 0 {
i += 1
}
if i - start > best_length {
best_start = start
best_length = i - start
}
}
let builder = StringBuilder()
let mut i = 0
while i < hex_groups {
if i == best_start {
builder.write_string("::")
i += best_length
continue
}
if i > 0 && i != best_start + best_length {
builder.write_char(':')
}
builder.write_string(groups[i].to_string(radix=16))
i += 1
}
if dotted {
if best_start < 0 || best_start + best_length != hex_groups {
builder.write_char(':')
}
let octets = [
groups[6] >> 8,
groups[6] & 255,
groups[7] >> 8,
groups[7] & 255,
]
builder.write_string(octets.map(octet => octet.to_string()).join("."))
}
builder.to_string()
}
///|
/// The four octets of a dotted quad without leading zeros (RFC 3986
/// `dec-octet`), or None.
fn ipv4_octets(text : StringView) -> Array[Int32]? {
let octets = []
for octet in text.split(".") {
guard octet.length() >= 1 &&
octet.length() <= 3 &&
octet.iter().all(c => c.is_ascii_digit()) &&
!(octet.length() > 1 && octet.has_prefix("0")) else {
return None
}
let value = octet
.iter()
.fold(init=0, (value, c) => value * 10 + c.to_int() - '0')
guard value <= 255 else { return None }
octets.push(value)
}
guard octets.length() == 4 else { return None }
Some(octets)
}
///|
fn hex_value(c : Char) -> Int32 {
match c {
'0'..<='9' => c.to_int() - '0'
'a'..<='f' => c.to_int() - 'a' + 10
_ => c.to_int() - 'A' + 10
}
}