///|
/// Address families are distinct, including IPv4-mapped IPv6 addresses.
pub(all) enum Family {
V4
V6
} derive(Eq, Compare)
///|
pub extend Family with Eq::{equal, not_equal}
///|
pub extend Family with Compare::{compare, op_lt, op_le, op_gt, op_ge}
///|
pub extend Family with Show::{output, to_string}
///|
pub impl Show for Family with fn output(self, logger) {
logger.write_string(if self == V4 { "IPv4" } else { "IPv6" })
}
///|
/// Invalid input is reported without constructing an invalid address or range.
pub(all) suberror IpError {
InvalidAddress(String)
InvalidPrefix(String)
InvalidRange(String)
HostBitsSet(String)
} derive(Eq)
///|
pub extend IpError with Eq::{equal, not_equal}
///|
pub extend IpError with Show::{output, to_string}
///|
pub impl Show for IpError with fn output(self, logger) {
match self {
InvalidAddress(text) => logger.write_string("Invalid IP address: \{text}")
InvalidPrefix(text) => logger.write_string("Invalid CIDR prefix: \{text}")
InvalidRange(text) => logger.write_string("Invalid address range: \{text}")
HostBitsSet(text) =>
logger.write_string("CIDR has nonzero host bits: \{text}")
}
}
///|
/// An immutable address. The four words are in network (most significant first) order.
struct Address {
family_ : Family
a : UInt32
b : UInt32
c : UInt32
d : UInt32
} derive(Eq)
///|
pub extend Address with Eq::{equal, not_equal}
///|
pub extend Address with Compare::{compare, op_lt, op_le, op_gt, op_ge}
///|
pub extend Address with Show::{output}
///|
pub impl Compare for Address with fn compare(self, other) {
if self.family_ != other.family_ {
if self.family_ == V4 {
-1
} else {
1
}
} else if self.a != other.a {
self.a.compare(other.a)
} else if self.b != other.b {
self.b.compare(other.b)
} else if self.c != other.c {
self.c.compare(other.c)
} else {
self.d.compare(other.d)
}
}
///|
/// Returns the address family, without interpreting mapped addresses as IPv4.
pub fn Address::family(self : Address) -> Family {
self.family_
}
///|
/// Returns the width of this address family in bits.
pub fn Address::width(self : Address) -> Int32 {
if self.family_ == V4 {
32
} else {
128
}
}
///|
fn parse_v4_word(text : String, original : String) -> UInt32 raise IpError {
let parts = text.split(".").to_array()
if parts.length() != 4 {
raise InvalidAddress(original)
}
let mut result = 0U
for part in parts {
if part.length() == 0 || part.length() > 3 {
raise InvalidAddress(original)
}
if part.length() > 1 && part[0].to_int() == 48 {
raise InvalidAddress(original)
}
let mut value = 0U
for unit in part.code_units() {
let digit = unit.to_int() - 48
if digit < 0 || digit > 9 {
raise InvalidAddress(original)
}
value = value * 10U + digit.reinterpret_as_uint()
}
if value > 255U {
raise InvalidAddress(original)
}
result = (result << 8) | value
}
result
}
///|
fn parse_v6_groups(
text : String,
allow_v4 : Bool,
original : String,
) -> Array[UInt32] raise IpError {
let result : Array[UInt32] = []
if text.is_empty() {
return result
}
let groups = text.split(":").to_array()
for index = 0; index < groups.length(); index = index + 1 {
let group = groups[index].to_owned()
if group.is_empty() {
raise InvalidAddress(original)
}
if group.contains(".") {
if !allow_v4 || index != groups.length() - 1 {
raise InvalidAddress(original)
}
let word = parse_v4_word(group, original)
result.push(word >> 16)
result.push(word & 65535U)
} else {
if group.length() > 4 {
raise InvalidAddress(original)
}
let mut word = 0U
for unit in group.code_units() {
let code = unit.to_int()
let digit = if code >= 48 && code <= 57 {
code - 48
} else if code >= 65 && code <= 70 {
code - 55
} else if code >= 97 && code <= 102 {
code - 87
} else {
raise InvalidAddress(original)
}
word = (word << 4) | digit.reinterpret_as_uint()
}
result.push(word)
}
}
result
}
///|
/// Parses strict IPv4 or IPv6 text; rejects whitespace, zones, brackets, and ports.
pub fn Address::parse(text : String) -> Address raise IpError {
if text.is_empty() || text.length() > 45 {
raise InvalidAddress(text)
}
if !text.contains(":") {
return { family_: V4, a: 0U, b: 0U, c: 0U, d: parse_v4_word(text, text), }
}
let groups : Array[UInt32] = match text.split_once("::") {
None => {
let full = parse_v6_groups(text, true, text)
if full.length() != 8 {
raise InvalidAddress(text)
}
full
}
Some((left, right)) => {
if right.contains("::") {
raise InvalidAddress(text)
}
let before = parse_v6_groups(left.to_owned(), false, text)
let after = parse_v6_groups(right.to_owned(), true, text)
let missing = 8 - before.length() - after.length()
if missing < 1 {
raise InvalidAddress(text)
}
for i = 0; i < missing; i = i + 1 {
before.push(0U)
}
for word in after {
before.push(word)
}
before
}
}
{
family_: V6,
a: (groups[0] << 16) | groups[1],
b: (groups[2] << 16) | groups[3],
c: (groups[4] << 16) | groups[5],
d: (groups[6] << 16) | groups[7],
}
}
///|
/// Formats IPv4 dotted decimal or lowercase compressed IPv6 (including mapped addresses).
pub fn Address::to_string(self : Address) -> String {
if self.family_ == V4 {
return "\{self.d >> 24}.\{(self.d >> 16) & 255U}.\{(self.d >> 8) & 255U}.\{self.d & 255U}"
}
let groups = [
self.a >> 16,
self.a & 65535U,
self.b >> 16,
self.b & 65535U,
self.c >> 16,
self.c & 65535U,
self.d >> 16,
self.d & 65535U,
]
let mut best_start = -1
let mut best_length = 1
let mut index = 0
while index < 8 {
if groups[index] == 0U {
let start = index
while index < 8 && groups[index] == 0U {
index = index + 1
}
let length = index - start
if length > best_length {
best_start = start
best_length = length
}
} else {
index = index + 1
}
}
let out = StringBuilder()
index = 0
while index < 8 {
if index == best_start {
out.write_string("::")
index = index + best_length
} else {
if index > 0 && index != best_start + best_length {
out.write_string(":")
}
out.write_string(groups[index].to_string(radix=16))
index = index + 1
}
}
out.to_string()
}
///|
pub impl Show for Address with fn output(self, logger) {
logger.write_string(self.to_string())
}
///|
/// Returns the next address in the same family, or None at its maximum value.
pub fn Address::next(self : Address) -> Address? {
if self.d != 4294967295U {
Some({ ..self, d: self.d + 1U, })
} else if self.family_ == V4 {
None
} else if self.c != 4294967295U {
Some({ ..self, c: self.c + 1U, d: 0U, })
} else if self.b != 4294967295U {
Some({ ..self, b: self.b + 1U, c: 0U, d: 0U, })
} else if self.a != 4294967295U {
Some({ ..self, a: self.a + 1U, b: 0U, c: 0U, d: 0U, })
} else {
None
}
}
///|
/// Returns the preceding address in the same family, or None at zero.
pub fn Address::previous(self : Address) -> Address? {
if self.d != 0U {
Some({ ..self, d: self.d - 1U, })
} else if self.family_ == V4 {
None
} else if self.c != 0U {
Some({ ..self, c: self.c - 1U, d: 4294967295U, })
} else if self.b != 0U {
Some({ ..self, b: self.b - 1U, c: 4294967295U, d: 4294967295U, })
} else if self.a != 0U {
Some({
..self,
a: self.a - 1U,
b: 4294967295U,
c: 4294967295U,
d: 4294967295U,
})
} else {
None
}
}
///|
fn word_mask(bits : Int32) -> UInt32 {
if bits <= 0 {
0U
} else if bits >= 32 {
4294967295U
} else {
4294967295U << (32 - bits)
}
}
///|
fn Address::mask(self : Address, prefix : Int32) -> Address {
let bits = if self.family_ == V4 { prefix + 96 } else { prefix }
{
..self,
a: self.a & word_mask(bits),
b: self.b & word_mask(bits - 32),
c: self.c & word_mask(bits - 64),
d: self.d & word_mask(bits - 96),
}
}
///|
fn Address::block_end(self : Address, prefix : Int32) -> Address {
let bits = if self.family_ == V4 { prefix + 96 } else { prefix }
{
..self,
a: self.a | word_mask(bits).lnot(),
b: self.b | word_mask(bits - 32).lnot(),
c: self.c | word_mask(bits - 64).lnot(),
d: self.d | word_mask(bits - 96).lnot(),
}
}
///|
fn Address::trailing_zeros(self : Address) -> Int32 {
if self.d != 0U || self.family_ == V4 {
self.d.ctz()
} else if self.c != 0U {
32 + self.c.ctz()
} else if self.b != 0U {
64 + self.b.ctz()
} else {
96 + self.a.ctz()
}
}