// Copyright 2025 International Digital Economy Academy
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
///|
fn try_parse_ipv6(
input : String,
) -> (FixedArray[Byte], Int, String?) raise InvalidAddr {
// Make sure the format is [addr]:port
guard input.has_prefix("[") else {
// IPv6 address must start with '['
raise InvalidAddr
}
// Find the closing bracket
guard input.rev_find("]") is Some(bracket_end) else {
// IPv6 address missing closing ']'
raise InvalidAddr
}
// Check the part after the closing bracket, it must be ":port"
let remaining = input[bracket_end + 1:]
guard remaining.has_prefix(":") else {
// Missing port after IPv6 address
raise InvalidAddr
}
// Parse the IPv6 address and port
let ip_str = input[1:bracket_end]
let port_str = remaining[1:]
// Extract optional zone suffix
let (ip_str, interface) = if ip_str.find("%") is Some(ip_end) {
let interface = ip_str[ip_end + 1:]
guard interface.length() > 0 else { raise InvalidAddr }
(ip_str[:ip_end], Some(interface.to_owned()))
} else {
(ip_str, None)
}
// Parse the IPv6 address into a byte array
let ip_bytes = parse_ipv6_to_bytes(ip_str)
let port = parse_port(port_str)
(ip_bytes, port, interface)
}
///|
fn parse_ipv6_to_bytes(
ip_str : StringView,
) -> FixedArray[Byte] raise InvalidAddr {
// Handle the case where the IPv6 address is "::"
if ip_str == "::" {
return FixedArray::make(16, 0)
}
// Check for "::" compression
match ip_str.split("::").collect() {
[ip_str] => parse_full_ipv6_to_bytes(ip_str)
[left_part, right_part] =>
parse_compressed_ipv6_to_bytes(left_part, right_part)
_ =>
// IPv6 address can only have one '::' compression
raise InvalidAddr
}
}
///|
fn parse_compressed_ipv6_to_bytes(
left_part : StringView,
right_part : StringView,
) -> FixedArray[Byte] raise InvalidAddr {
let left_groups = if left_part.is_empty() {
[]
} else {
left_part.split(":").collect()
}
// Parse the right part, which may contain IPv4 address
let right_groups = if right_part.is_empty() {
[]
} else {
right_part.split(":").collect()
}
// Check if the last group is an IPv4 address
let ipv4_bytes = if !right_groups.is_empty() &&
right_groups[right_groups.length() - 1].contains(".") {
// Remove the IPv4 part from the right groups
Some(parse_ipv4_to_bytes(right_groups.unsafe_pop()))
} else {
None
}
// Calculate the number of groups of compression
let ipv4_group_count = if ipv4_bytes is None { 0 } else { 2 }
let total_explicit_groups = left_groups.length() +
right_groups.length() +
ipv4_group_count
guard total_explicit_groups < 8 else {
// IPv6 address can have at most less than 8 explicit groups if "::" is used
raise InvalidAddr
}
let compression_groups = 8 - total_explicit_groups
// Build a 16-byte array initialized to zero
let result : FixedArray[Byte] = FixedArray::make(16, 0)
let mut byte_index = 0
// Add left side groups
for group_str in left_groups {
let group_value = parse_ipv6_group(group_str)
result[byte_index] = ((group_value >> 8) & 0xFF).to_byte()
result[byte_index + 1] = (group_value & 0xFF).to_byte()
byte_index += 2
}
// Add compression groups (zero groups)
for _ in 0..> 8) & 0xFF).to_byte()
result[byte_index + 1] = (group_value & 0xFF).to_byte()
byte_index += 2
}
// Add IPv4 bytes if they were parsed
if ipv4_bytes is Some((a, b, c, d)) {
result[byte_index + 0] = a.to_byte()
result[byte_index + 1] = b.to_byte()
result[byte_index + 2] = c.to_byte()
result[byte_index + 3] = d.to_byte()
}
result
}
///|
fn parse_full_ipv6_to_bytes(
ip_str : StringView,
) -> FixedArray[Byte] raise InvalidAddr {
let parts = ip_str.split(":").collect()
let result : FixedArray[Byte] = FixedArray::make(16, 0)
// Check if the last part contains a dot, indicating an embedded IPv4 address
let ipv4_bytes = if parts[parts.length() - 1].contains(".") {
guard parts.length() == 7 else {
// IPv6 address with embedded IPv4 must have 7 parts
raise InvalidAddr
}
Some(parse_ipv4_to_bytes(parts.unsafe_pop()))
} else {
guard parts.length() == 8 else {
// Full IPv6 address must have 8 groups
raise InvalidAddr
}
None
}
// Build a 16-byte array initialized to zero
let mut byte_index = 0
for part in parts {
let group_value = parse_ipv6_group(part)
result[byte_index] = ((group_value >> 8) & 0xFF).to_byte()
result[byte_index + 1] = (group_value & 0xFF).to_byte()
byte_index += 2
}
if ipv4_bytes is Some((a, b, c, d)) {
result[byte_index + 0] = a.to_byte()
result[byte_index + 1] = b.to_byte()
result[byte_index + 2] = c.to_byte()
result[byte_index + 3] = d.to_byte()
}
result
}
///|
fn parse_ipv6_group(group : StringView) -> UInt raise InvalidAddr {
guard !group.is_empty() && group.length() <= 4 else { raise InvalidAddr }
@string.parse_uint(group, base=16) catch {
_ => raise InvalidAddr
}
}
///|
fn parse_ipv6_zone_suffix(zone : StringView, context~ : String) -> UInt raise {
try @string.parse_uint(zone) catch {
_ => if_nametoindex(zone, context~)
} noraise {
index => index
}
}
///|
fn parse_ipv4_to_bytes(
ip_str : StringView,
) -> (UInt, UInt, UInt, UInt) raise InvalidAddr {
let parts = ip_str.split(".").collect()
guard parts.length() == 4 else { raise InvalidAddr }
fn parse_octal(x : StringView) raise InvalidAddr {
let value = @string.parse_uint(x) catch { _ => raise InvalidAddr }
guard value < 256 else { raise InvalidAddr }
value
}
return (
parse_octal(parts[0]),
parse_octal(parts[1]),
parse_octal(parts[2]),
parse_octal(parts[3]),
)
}
///|
fn try_parse_ipv4(input : String) -> (UInt, Int) raise InvalidAddr {
// Find the last colon to separate IP and port
guard input.rev_find(":") is Some(last_colon) else { raise InvalidAddr }
let ip_str = input[:last_colon]
let port_str = input[last_colon + 1:]
let port = parse_port(port_str)
// Parse the IP address
let (a, b, c, d) = parse_ipv4_to_bytes(ip_str.view())
let ip = (a << 24) | (b << 16) | (c << 8) | d
(ip, port)
}
///|
fn parse_port(port_str : StringView) -> Int raise InvalidAddr {
guard !port_str.is_empty() else { raise InvalidAddr }
let port = @string.parse_int(port_str) catch { _ => raise InvalidAddr }
guard 0 <= port && port < 65536 else { raise InvalidAddr }
port
}
///|
fn write_ipv6_str(addr : BytesView, logger : &Logger) -> Unit {
let mut longest_zero_group = None
fn add_zero_group(start, end) {
if end > start {
match longest_zero_group {
Some((start0, end0)) if end0 - start0 >= end - start => ()
_ => longest_zero_group = Some((start, end))
}
}
}
for i = 0, zero_group_start = 0; i < 8; i = i + 1 {
if addr[i * 2] == 0 && addr[i * 2 + 1] == 0 {
continue
} else {
add_zero_group(zero_group_start, i)
continue i + 1, i + 1
}
} nobreak {
add_zero_group(zero_group_start, i)
}
let is_v4_mapped = match longest_zero_group {
Some((0, 6)) => true
Some((0, 5)) => addr[10] is 0xff && addr[11] is 0xff
_ => false
}
let hex_format_end = if is_v4_mapped { 6 } else { 8 }
for i = 0; i < hex_format_end; i = i + 1 {
match longest_zero_group {
Some((start, end)) if i == start => {
if i == 0 {
logger <+ "::"
} else {
logger <+ ":"
}
continue end
}
_ => ()
}
let value = (addr[i * 2].to_int() << 8) | addr[i * 2 + 1].to_int()
logger <+ "\{value.to_string(radix=16)}"
if i < 7 {
logger <+ ":"
}
}
if is_v4_mapped {
logger <+
"\{addr[12].to_int()}.\{addr[13].to_int()}.\{addr[14].to_int()}.\{addr[15].to_int()}"
}
}