// Simplified IP address library for MoonBit
// This is a basic implementation focusing on core functionality
// IPv4 address representation
///|
/// Error types for IP address parsing and validation
pub enum IpAddrError {
InvalidFormat
InvalidOctet(Int)
} derive(Eq, Debug, Hash)
///|
pub struct Ipv4(Byte, Byte, Byte, Byte) derive(Eq, Compare, Debug, Hash)
///|
/// Creates a new IPv4 address from four octets.
///
/// # Parameters
/// - `a`: First octet (0-255)
/// - `b`: Second octet (0-255)
/// - `c`: Third octet (0-255)
/// - `d`: Fourth octet (0-255)
///
/// # Examples
/// ```moonbit nocheck
/// let _ = @ipaddr.ipv4(127, 0, 0, 1)
/// let _ = @ipaddr.ipv4(192, 168, 1, 100)
/// let _ = @ipaddr.ipv4(8, 8, 8, 8)
/// ```
///
/// # Note
/// This function does not validate that the octets are in the valid range (0-255).
/// Use `Ipv4::is_valid()` to check if the resulting address is valid.
///
pub fn ipv4(a : Byte, b : Byte, c : Byte, d : Byte) -> Ipv4 {
Ipv4(a, b, c, d)
}
///|
/// Creates a new IPv4 address from four octets (OO-style constructor).
///
/// # Parameters
/// - `a`: First octet (0-255)
/// - `b`: Second octet (0-255)
/// - `c`: Third octet (0-255)
/// - `d`: Fourth octet (0-255)
///
/// # Examples
/// ```moonbit nocheck
/// let _ = @ipaddr.Ipv4::new(127, 0, 0, 1)
/// let _ = @ipaddr.Ipv4::new(192, 168, 1, 100)
/// let _ = @ipaddr.Ipv4::new(8, 8, 8, 8)
/// ```
///
/// # Note
/// This function does not validate that the octets are in the valid range (0-255).
/// Use `Ipv4::is_valid()` to check if the resulting address is valid.
///
pub fn Ipv4::new(a : Byte, b : Byte, c : Byte, d : Byte) -> Ipv4 {
Ipv4(a, b, c, d)
}
///|
/// Parses an IPv4 address from a dotted decimal string.
///
/// # Parameters
/// - `s`: A string in the format "a.b.c.d" where each component is 0-255
///
/// # Returns
/// `Some(Ipv4)` if parsing succeeds, `None` if the format is invalid
///
/// # Examples
/// ```moonbit nocheck
/// let _ = @ipaddr.Ipv4::parse("192.168.1.1")
/// let _ = @ipaddr.Ipv4::parse("127.0.0.1")
/// let _ = @ipaddr.Ipv4::parse("256.1.1.1") // None - out of range
/// let _ = @ipaddr.Ipv4::parse("192.168.1") // None - wrong format
/// ```
///
fn parse_octet(s : String) -> Byte? {
if s.length() == 0 || s.length() > 3 {
return None
}
let mut result = 0
let mut i = 0
while i < s.length() {
let c = s[i]
if c < '0' || c > '9' {
return None
}
result = result * 10 + (c - 48).to_int()
if result > 255 {
return None
}
i = i + 1
}
// Don't allow leading zeros (except for "0")
if s.length() > 1 && s[0] == '0' {
return None
}
Some(result.to_byte())
}
///|
fn parse_octet_detailed(s : String) -> Result[Byte, IpAddrError] {
if s.length() == 0 {
return Err(InvalidFormat)
}
if s.length() > 3 {
return Err(InvalidFormat)
}
let mut result = 0
let mut i = 0
while i < s.length() {
let c = s[i]
if c < '0' || c > '9' {
return Err(InvalidFormat)
}
result = result * 10 + (c - 48).to_int()
if result > 255 {
return Err(InvalidOctet(result))
}
i = i + 1
}
// Don't allow leading zeros (except for "0")
if s.length() > 1 && s[0] == '0' {
return Err(InvalidFormat)
}
Ok(result.to_byte())
}
///|
pub fn Ipv4::parse(s : String) -> Ipv4? {
let parts = s.split(".").collect()
if parts.length() != 4 {
return None
}
let octets : Array[Byte] = []
for i = 0; i < 4; i = i + 1 {
match parse_octet(parts[i].to_string()) {
Some(n) => octets.push(n)
None => return None
}
}
Some(ipv4(octets[0], octets[1], octets[2], octets[3]))
}
///|
/// Parses an IPv4 address from a dotted decimal string with detailed error information.
///
/// This is an enhanced version of `parse` that provides specific error details
/// instead of just returning `None` on failure.
///
/// # Parameters
/// - `s`: A string in the format "a.b.c.d" where each component is 0-255
///
/// # Returns
/// `Ok(Ipv4)` if parsing succeeds, `Err(IpAddrError)` with specific error details
///
/// # Examples
/// ```moonbit nocheck
/// let _ = @ipaddr.Ipv4::parse_detailed("192.168.1.1")
/// let _ = @ipaddr.Ipv4::parse_detailed("256.1.1.1") // Err(InvalidOctet(256))
/// let _ = @ipaddr.Ipv4::parse_detailed("192.168.1") // Err(InvalidFormat)
/// ```
///
pub fn Ipv4::parse_detailed(s : String) -> Result[Ipv4, IpAddrError] {
let parts = s.split(".").collect()
if parts.length() != 4 {
return Err(InvalidFormat)
}
let octets : Array[Byte] = []
for i = 0; i < 4; i = i + 1 {
match parse_octet_detailed(parts[i].to_string()) {
Ok(n) => octets.push(n)
Err(e) => return Err(e)
}
}
Ok(ipv4(octets[0], octets[1], octets[2], octets[3]))
}
///|
/// Formats the IPv4 address as a dotted decimal string.
///
/// # Returns
/// A string representation in the format "a.b.c.d"
///
/// # Examples
/// ```moonbit nocheck
/// let addr = @ipaddr.ipv4(192, 168, 1, 1)
/// let _ = addr.format() // "192.168.1.1"
///
/// let localhost = @ipaddr.ipv4(127, 0, 0, 1)
/// let _ = localhost.format() // "127.0.0.1"
/// ```
///
pub fn Ipv4::format(self : Self) -> String {
"\{self.0.to_int()}.\{self.1.to_int()}.\{self.2.to_int()}.\{self.3.to_int()}"
}
///|
/// Private helper to check if a byte is a valid IPv4 octet (0-255).
fn is_valid_octet(n : Byte) -> Bool {
n >= 0 && n <= 255
}
/// Checks if all octets of the IPv4 address are valid (0-255).
///
/// # Returns
/// `true` if all octets are in the valid range [0, 255], `false` otherwise
///
/// # Examples
/// ```moonbit
/// let valid_addr = @ipaddr.ipv4(192, 168, 1, 1)
/// let _ = valid = valid_addr.is_valid() // true
///
/// let invalid_addr = @ipaddr.ipv4(256, 168, 1, 1)
/// let _ = invalid = invalid_addr.is_valid() // false
///
/// let edge_addr = @ipaddr.ipv4(0, 0, 0, 0)
/// let _ = edge_valid = edge_addr.is_valid() // true
/// ```
///|
pub fn Ipv4::is_valid(self : Self) -> Bool {
is_valid_octet(self.0) &&
is_valid_octet(self.1) &&
is_valid_octet(self.2) &&
is_valid_octet(self.3)
}
///|
/// Converts the IPv4 address to a 32-bit integer in network byte order.
///
/// The conversion follows the standard network byte order (big-endian):
/// - First octet becomes the most significant byte
/// - Fourth octet becomes the least significant byte
///
/// # Returns
/// A 32-bit integer representation of the IPv4 address
///
/// # Examples
/// ```moonbit nocheck
/// let addr = @ipaddr.ipv4(192, 168, 1, 1)
/// let _ = addr.to_int() // 3232235777
///
/// let localhost = @ipaddr.ipv4(127, 0, 0, 1)
/// let _ = localhost.to_int() // 2130706433
///
/// let zero = @ipaddr.ipv4(0, 0, 0, 0)
/// let _ = zero.to_int() // 0
/// ```
///
pub fn Ipv4::to_int(self : Self) -> Int {
(self.0.to_int() << 24) |
(self.1.to_int() << 16) |
(self.2.to_int() << 8) |
self.3.to_int()
}
///|
/// Creates an IPv4 address from a 32-bit integer in network byte order.
///
/// The conversion follows the standard network byte order (big-endian):
/// - Most significant byte becomes the first octet
/// - Least significant byte becomes the fourth octet
///
/// # Parameters
/// - `n`: A 32-bit integer representing the IPv4 address
///
/// # Returns
/// An IPv4 address constructed from the integer
///
/// # Examples
/// ```moonbit nocheck
/// let addr = @ipaddr.Ipv4::from_int(0x7F000001) // 127.0.0.1
/// let _ = addr.format() // "127.0.0.1"
///
/// let localhost = @ipaddr.Ipv4::from_int(0x7F000001) // 127.0.0.1
/// let _ = localhost.format() // "127.0.0.1"
///
/// let _ = @ipaddr.Ipv4::from_int(0) // 0.0.0.0
/// ```
///
pub fn Ipv4::from_int(n : Int) -> Ipv4 {
let a = ((n >> 24) & 0xFF).to_byte()
let b = ((n >> 16) & 0xFF).to_byte()
let c = ((n >> 8) & 0xFF).to_byte()
let d = (n & 0xFF).to_byte()
ipv4(a, b, c, d)
}
///|
/// Creates an IPv4 address from a 32-bit integer with validation.
///
/// This is a safe version of `from_int` that validates the input range.
/// Unlike `from_int`, this function returns `None` for negative values
/// or values that exceed the valid 32-bit unsigned range.
///
/// # Parameters
/// - `n`: A 32-bit integer (0 to 4294967295)
///
/// # Returns
/// `Some(Ipv4)` if the integer is valid, `None` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// let _ = @ipaddr.Ipv4::try_from_int(0x7F000001) // Some(127.0.0.1)
/// let _ = @ipaddr.Ipv4::try_from_int(0) // Some(0.0.0.0)
/// let _ = @ipaddr.Ipv4::try_from_int(-1) // None - negative
/// ```
///
pub fn Ipv4::try_from_int(n : Int) -> Ipv4? {
if n < 0 {
return None
}
// For 32-bit values, we don't need to check overflow in MoonBit
// since integers are 64-bit and we're only using the lower 32 bits
Some(Ipv4::from_int(n))
}
///|
/// Checks if the IPv4 address is a private address as defined by RFC 1918.
///
/// Private address ranges are:
/// - 10.0.0.0/8 (10.0.0.0 to 10.255.255.255)
/// - 172.16.0.0/12 (172.16.0.0 to 172.31.255.255)
/// - 192.168.0.0/16 (192.168.0.0 to 192.168.255.255)
///
/// # Returns
/// `true` if the address is in a private range, `false` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// let private1 = @ipaddr.ipv4(10, 0, 0, 1)
/// let _ = private1.is_private() // true
///
/// let private2 = @ipaddr.ipv4(172, 16, 0, 1)
/// let _ = private2.is_private() // true
///
/// let private3 = @ipaddr.ipv4(192, 168, 1, 1)
/// let _ = private3.is_private() // true
///
/// let public_addr = @ipaddr.ipv4(8, 8, 8, 8)
/// let _ = public_addr.is_private() // false
/// ```
///
pub fn Ipv4::is_private(self : Self) -> Bool {
// 10.0.0.0/8
if self.0 == 10 {
return true
}
// 172.16.0.0/12
if self.0 == 172 && self.1 >= 16 && self.1 <= 31 {
return true
}
// 192.168.0.0/16
if self.0 == 192 && self.1 == 168 {
return true
}
false
}
///|
/// Checks if the IPv4 address is a loopback address.
///
/// Loopback addresses are in the range 127.0.0.0/8 (127.0.0.0 to 127.255.255.255).
/// These addresses are used to refer to the local host.
///
/// # Returns
/// `true` if the address is a loopback address, `false` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// let localhost = @ipaddr.ipv4(127, 0, 0, 1)
/// let _ = localhost.is_loopback() // true
///
/// let loopback_variant = @ipaddr.ipv4(127, 1, 2, 3)
/// let _ = loopback_variant.is_loopback() // true
///
/// let not_loopback = @ipaddr.ipv4(192, 168, 1, 1)
/// let _ = not_loopback.is_loopback() // false
/// ```
///
pub fn Ipv4::is_loopback(self : Self) -> Bool {
self.0 == 127
}
///|
/// Checks if the IPv4 address is a multicast address.
///
/// Multicast addresses are in the range 224.0.0.0/4 (224.0.0.0 to 239.255.255.255).
/// These addresses are used for multicast communication where data is sent to
/// multiple recipients simultaneously.
///
/// # Returns
/// `true` if the address is a multicast address, `false` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// let multicast1 = @ipaddr.ipv4(224, 0, 0, 1)
/// let _ = multicast1.is_multicast() // true
///
/// let multicast2 = @ipaddr.ipv4(239, 255, 255, 255)
/// let _ = multicast2.is_multicast() // true
///
/// let unicast = @ipaddr.ipv4(192, 168, 1, 1)
/// let _ = unicast.is_multicast() // false
/// ```
///
pub fn Ipv4::is_multicast(self : Self) -> Bool {
self.0 >= 224 && self.0 <= 239
}
///|
/// Checks if the IPv4 address is the limited broadcast address.
///
/// The limited broadcast address is 255.255.255.255, which is used to send
/// packets to all hosts on the local network segment.
///
/// # Returns
/// `true` if the address is 255.255.255.255, `false` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// let broadcast = @ipaddr.ipv4(255, 255, 255, 255)
/// let _ = broadcast.is_broadcast() // true
///
/// let not_broadcast = @ipaddr.ipv4(192, 168, 1, 255)
/// let _ = not_broadcast.is_broadcast() // false
///
/// let almost_broadcast = @ipaddr.ipv4(255, 255, 255, 254)
/// let _ = almost_broadcast.is_broadcast() // false
/// ```
///
pub fn Ipv4::is_broadcast(self : Self) -> Bool {
self.0 == 255 && self.1 == 255 && self.2 == 255 && self.3 == 255
}
///|
/// IPv4 CIDR prefix
///
pub struct Ipv4Prefix {
addr : Ipv4
prefix_len : Int
} derive(Eq, Compare, Debug, Hash)
///|
/// Creates a new IPv4 CIDR prefix from an address and prefix length.
///
/// A CIDR prefix represents a network subnet using an IPv4 address and a prefix length.
/// The prefix length indicates how many bits from the left are used for the network portion.
///
/// # Parameters
/// - `addr`: The IPv4 address (typically the network address)
/// - `prefix_len`: The prefix length in bits (0-32)
///
/// # Returns
/// An Ipv4Prefix representing the network subnet
///
/// # Examples
/// ```moonbit nocheck
/// // Create a /24 subnet (255.255.255.0 netmask)
/// let _ = @ipaddr.ipv4_prefix(@ipaddr.ipv4(192, 168, 1, 0), 24)
///
/// // Create a /16 subnet (255.255.0.0 netmask)
/// let _ = @ipaddr.ipv4_prefix(@ipaddr.ipv4(10, 0, 0, 0), 16)
///
/// // Create a /32 host route (255.255.255.255 netmask)
/// let _ = @ipaddr.ipv4_prefix(@ipaddr.ipv4(8, 8, 8, 8), 32)
/// ```
///
pub fn ipv4_prefix(addr : Ipv4, prefix_len : Int) -> Ipv4Prefix {
{ addr, prefix_len }
}
///|
/// Creates a new IPv4 CIDR prefix from an address and prefix length (OO-style constructor).
///
/// A CIDR prefix represents a network subnet using an IPv4 address and a prefix length.
/// The prefix length indicates how many bits from the left are used for the network portion.
///
/// # Parameters
/// - `addr`: The IPv4 address (typically the network address)
/// - `prefix_len`: The prefix length in bits (0-32)
///
/// # Returns
/// An Ipv4Prefix representing the network subnet
///
/// # Examples
/// ```moonbit nocheck
/// // Create a /24 subnet (255.255.255.0 netmask)
/// let _ = @ipaddr.Ipv4Prefix::new(@ipaddr.Ipv4::new(192, 168, 1, 0), 24)
///
/// // Create a /16 subnet (255.255.0.0 netmask)
/// let _ = @ipaddr.Ipv4Prefix::new(@ipaddr.Ipv4::new(10, 0, 0, 0), 16)
///
/// // Create a /32 host route (255.255.255.255 netmask)
/// let _ = @ipaddr.Ipv4Prefix::new(@ipaddr.Ipv4::new(8, 8, 8, 8), 32)
/// ```
///
pub fn Ipv4Prefix::new(addr : Ipv4, prefix_len : Int) -> Ipv4Prefix {
{ addr, prefix_len }
}
///|
/// Parses an IPv4 CIDR prefix from a string.
///
/// # Parameters
/// - `s`: A string in the format "a.b.c.d/n" where a.b.c.d is an IPv4 address and n is the prefix length (0-32)
///
/// # Returns
/// `Some(Ipv4Prefix)` if parsing succeeds, `None` if the format is invalid
///
/// # Examples
/// ```moonbit nocheck
/// let _ = @ipaddr.Ipv4Prefix::parse("192.168.1.0/24")
/// let _ = @ipaddr.Ipv4Prefix::parse("10.0.0.0/8")
/// let _ = @ipaddr.Ipv4Prefix::parse("invalid") // None - wrong format
/// ```
///
pub fn Ipv4Prefix::parse(s : String) -> Ipv4Prefix? {
let parts = s.split("/").collect()
if parts.length() != 2 {
return None
}
// Parse the IPv4 address part
let addr = match Ipv4::parse(parts[0].to_string()) {
Some(a) => a
None => return None
}
// Parse the prefix length part
let prefix_len = match parse_prefix_len(parts[1].to_string()) {
Some(n) => n
None => return None
}
Some(ipv4_prefix(addr, prefix_len))
}
///|
fn parse_prefix_len(s : String) -> Int? {
if s.length() == 0 || s.length() > 2 {
return None
}
let mut result = 0
let mut i = 0
while i < s.length() {
let c = s[i]
if c < '0' || c > '9' {
return None
}
result = result * 10 + (c - 48).to_int()
if result > 32 {
return None
}
i = i + 1
}
// Don't allow leading zeros (except for single "0")
if s.length() > 1 && s[0] == '0' {
return None
}
Some(result)
}
///|
/// Checks if an IPv4 address is contained within this CIDR prefix/subnet.
///
/// This method determines if the given address belongs to the network defined
/// by this prefix by comparing the network portions of both addresses.
///
/// # Parameters
/// - `addr`: The IPv4 address to check
///
/// # Returns
/// `true` if the address is within the subnet, `false` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// let subnet = @ipaddr.ipv4_prefix(@ipaddr.ipv4(192, 168, 1, 0), 24)
///
/// let addr1 = @ipaddr.ipv4(192, 168, 1, 1)
/// let _ = subnet.contains(addr1) // true
///
/// let addr2 = @ipaddr.ipv4(192, 168, 1, 255)
/// let _ = subnet.contains(addr2) // true
///
/// let addr3 = @ipaddr.ipv4(192, 168, 2, 1)
/// let _ = subnet.contains(addr3) // false
///
/// // Special case: /0 contains all addresses
/// let all_networks = @ipaddr.ipv4_prefix(@ipaddr.ipv4(0, 0, 0, 0), 0)
/// let _ = all_networks.contains(@ipaddr.ipv4(8, 8, 8, 8)) // true
/// ```
///
pub fn Ipv4Prefix::contains(self : Self, addr : Ipv4) -> Bool {
if self.prefix_len == 0 {
return true
}
let prefix_int = self.addr.to_int()
let addr_int = addr.to_int()
let mask = (0xFFFFFFFF << (32 - self.prefix_len)) & 0xFFFFFFFF
(prefix_int & mask) == (addr_int & mask)
}
///|
/// Gets the network address for this IPv4 prefix.
///
/// The network address is the first address in the subnet, obtained by
/// setting all host bits to zero. This is also known as the subnet address.
///
/// # Returns
/// The network address as an IPv4 address
///
/// # Examples
/// ```moonbit nocheck
/// let prefix = @ipaddr.ipv4_prefix(@ipaddr.ipv4(192, 168, 1, 100), 24)
/// let network = prefix.network() // 192.168.1.0
/// let _ = network.format() // "192.168.1.0"
///
/// let prefix16 = @ipaddr.ipv4_prefix(@ipaddr.ipv4(10, 5, 10, 20), 16)
/// let _ = prefix16.network() // 10.5.0.0
///
/// // Special case: /0 network is 0.0.0.0
/// let default_route = @ipaddr.ipv4_prefix(@ipaddr.ipv4(8, 8, 8, 8), 0)
/// let _ = default_route.network() // 0.0.0.0
/// ```
///
pub fn Ipv4Prefix::network(self : Self) -> Ipv4 {
if self.prefix_len == 0 {
return ipv4(0, 0, 0, 0)
}
let addr_int = self.addr.to_int()
let mask = (0xFFFFFFFF << (32 - self.prefix_len)) & 0xFFFFFFFF
let network_int = addr_int & mask
Ipv4::from_int(network_int)
}
///|
/// Gets the broadcast address for this IPv4 prefix.
///
/// The broadcast address is the last address in the subnet, obtained by
/// setting all host bits to one. Packets sent to this address are delivered
/// to all hosts in the subnet.
///
/// # Returns
/// The broadcast address as an IPv4 address
///
/// # Examples
/// ```moonbit nocheck
/// let prefix = @ipaddr.ipv4_prefix(@ipaddr.ipv4(192, 168, 1, 0), 24)
/// let broadcast = prefix.broadcast() // 192.168.1.255
/// let _ = broadcast.format() // "192.168.1.255"
///
/// let prefix16 = @ipaddr.ipv4_prefix(@ipaddr.ipv4(10, 5, 0, 0), 16)
/// let _ = prefix16.broadcast() // 10.5.255.255
///
/// // Special case: /0 broadcast is 255.255.255.255
/// let default_route = @ipaddr.ipv4_prefix(@ipaddr.ipv4(0, 0, 0, 0), 0)
/// let _ = default_route.broadcast() // 255.255.255.255
/// ```
///
pub fn Ipv4Prefix::broadcast(self : Self) -> Ipv4 {
if self.prefix_len == 0 {
return ipv4(255, 255, 255, 255)
}
let addr_int = self.addr.to_int()
let mask = (0xFFFFFFFF << (32 - self.prefix_len)) & 0xFFFFFFFF
let broadcast_int = addr_int | (mask.lnot() & 0xFFFFFFFF)
Ipv4::from_int(broadcast_int)
}
///|
/// Returns the subnet mask for this CIDR prefix.
///
/// The subnet mask indicates which bits are used for the network portion
/// of the address. For example, a /24 prefix has a mask of 255.255.255.0.
///
/// # Returns
/// An Ipv4 address representing the subnet mask
///
/// # Examples
/// ```moonbit nocheck
/// let subnet = @ipaddr.Ipv4Prefix::new(@ipaddr.Ipv4::new(192, 168, 1, 0), 24)
/// let mask = subnet.mask()
/// let _ = mask.format() // "255.255.255.0"
///
/// let host_route = @ipaddr.Ipv4Prefix::new(@ipaddr.Ipv4::new(10, 0, 0, 1), 32)
/// let host_mask = host_route.mask()
/// let _ = host_mask.format() // "255.255.255.255"
///
/// let default_route = @ipaddr.Ipv4Prefix::new(@ipaddr.Ipv4::new(0, 0, 0, 0), 0)
/// let default_mask = default_route.mask()
/// let _ = default_mask.format() // "0.0.0.0"
/// ```
///
pub fn Ipv4Prefix::mask(self : Self) -> Ipv4 {
if self.prefix_len == 0 {
return Ipv4::from_int(0)
}
let mask_int = (0xFFFFFFFF << (32 - self.prefix_len)) & 0xFFFFFFFF
Ipv4::from_int(mask_int)
}
///|
/// Returns the number of host addresses in this subnet.
///
/// This includes the network and broadcast addresses. For usable host addresses,
/// subtract 2 (except for /31 and /32 subnets which have special rules).
///
/// # Returns
/// The total number of addresses in the subnet
///
/// # Examples
/// ```moonbit nocheck
/// let subnet = @ipaddr.Ipv4Prefix::new(@ipaddr.Ipv4::new(192, 168, 1, 0), 24)
/// let _ = subnet.host_count() // 256 (254 usable)
///
/// let host_route = @ipaddr.Ipv4Prefix::new(@ipaddr.Ipv4::new(10, 0, 0, 1), 32)
/// let _ = host_route.host_count() // 1
///
/// let point_to_point = @ipaddr.Ipv4Prefix::new(@ipaddr.Ipv4::new(10, 0, 0, 0), 31)
/// let _ = point_to_point.host_count() // 2
/// ```
///
pub fn Ipv4Prefix::host_count(self : Self) -> Int {
if self.prefix_len >= 32 {
return 1
}
1 << (32 - self.prefix_len)
}
///|
/// MAC address (6 bytes)
///
pub struct Mac(Byte, Byte, Byte, Byte, Byte, Byte) derive (
Eq,
Compare,
Debug,
Hash,
)
///|
/// Creates a new MAC address from six bytes.
///
/// MAC (Media Access Control) addresses are 48-bit identifiers used to uniquely
/// identify network interfaces. They are typically displayed in hexadecimal format
/// separated by colons (e.g., "00:11:22:33:44:55").
///
/// # Parameters
/// - `b0`: First byte (most significant)
/// - `b1`: Second byte
/// - `b2`: Third byte
/// - `b3`: Fourth byte
/// - `b4`: Fifth byte
/// - `b5`: Sixth byte (least significant)
///
/// # Examples
/// ```moonbit nocheck
/// // Create a typical MAC address
/// let _ = @ipaddr.mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
///
/// // Create a broadcast MAC address
/// let _ = @ipaddr.mac(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)
///
/// // Create a multicast MAC address (first bit of first byte is 1)
/// let _ = @ipaddr.mac(0x01, 0x00, 0x5E, 0x00, 0x00, 0x01)
/// ```
///
pub fn mac(
b0 : Byte,
b1 : Byte,
b2 : Byte,
b3 : Byte,
b4 : Byte,
b5 : Byte,
) -> Mac {
Mac(b0, b1, b2, b3, b4, b5)
}
///|
/// Creates a new MAC address from six bytes (OO-style constructor).
///
/// MAC (Media Access Control) addresses are 48-bit identifiers used to uniquely
/// identify network interfaces. They are typically displayed in hexadecimal format
/// separated by colons (e.g., "00:11:22:33:44:55").
///
/// # Parameters
/// - `b0`: First byte (most significant)
/// - `b1`: Second byte
/// - `b2`: Third byte
/// - `b3`: Fourth byte
/// - `b4`: Fifth byte
/// - `b5`: Sixth byte (least significant)
///
/// # Examples
/// ```moonbit nocheck
/// // Create a typical MAC address
/// let _ = @ipaddr.Mac::new(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
///
/// // Create a broadcast MAC address
/// let _ = @ipaddr.Mac::new(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)
///
/// // Create a multicast MAC address (first bit of first byte is 1)
/// let _ = @ipaddr.Mac::new(0x01, 0x00, 0x5E, 0x00, 0x00, 0x01)
/// ```
///
pub fn Mac::new(
b0 : Byte,
b1 : Byte,
b2 : Byte,
b3 : Byte,
b4 : Byte,
b5 : Byte,
) -> Mac {
Mac(b0, b1, b2, b3, b4, b5)
}
///|
/// Parses a MAC address from a hexadecimal string.
///
/// # Parameters
/// - `s`: A string in the format "xx:xx:xx:xx:xx:xx" or "xx-xx-xx-xx-xx-xx" where each xx is a hexadecimal byte
///
/// # Returns
/// `Some(Mac)` if parsing succeeds, `None` if the format is invalid
///
/// # Examples
/// ```moonbit nocheck
/// let _ = @ipaddr.Mac::parse("00:11:22:33:44:55")
/// let _ = @ipaddr.Mac::parse("FF-FF-FF-FF-FF-FF")
/// let _ = @ipaddr.Mac::parse("invalid") // None - wrong format
/// ```
///
pub fn Mac::parse(s : String) -> Mac? {
// Try colon separator first, then dash
let parts = if s.contains(":") {
s.split(":").collect()
} else if s.contains("-") {
s.split("-").collect()
} else {
return None
}
if parts.length() != 6 {
return None
}
let bytes : Array[Int] = []
for i = 0; i < 6; i = i + 1 {
match parse_hex_byte(parts[i].to_string()) {
Some(n) => bytes.push(n)
None => return None
}
}
Some(
mac(
bytes[0].to_byte(),
bytes[1].to_byte(),
bytes[2].to_byte(),
bytes[3].to_byte(),
bytes[4].to_byte(),
bytes[5].to_byte(),
),
)
}
///|
fn parse_hex_byte(s : String) -> Int? {
if s.length() != 2 {
return None
}
let mut result = 0
let mut i = 0
while i < 2 {
let c = s[i]
let digit = if c >= '0' && c <= '9' {
c - 48 // '0' = 48
} else if c >= 'a' && c <= 'f' {
c - 87 // 'a' = 97, so 'a' - 87 = 10
} else if c >= 'A' && c <= 'F' {
c - 55 // 'A' = 65, so 'A' - 55 = 10
} else {
return None
}
result = result * 16 + digit.to_int()
i = i + 1
}
Some(result)
}
///|
/// Helper function to format a byte as a two-digit lowercase hexadecimal string.
///
/// This internal utility function converts an integer (0-255) to a two-character
/// hexadecimal string with leading zeros if necessary. Used internally by MAC
/// address formatting.
///
/// # Parameters
/// - `n`: An integer in the range 0-255
///
/// # Returns
/// A two-character lowercase hexadecimal string
///
/// # Examples
/// ```moonbit nocheck
/// // Internal function, typically not called directly
/// // Internal function examples: "00", "0f", "ff", "ab"
/// ```
///
fn format_hex8(n : Int) -> String {
let hex_chars = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
]
let high = (n >> 4) & 0xF
let low = n & 0xF
String::from_array([hex_chars[high], hex_chars[low]])
}
///|
/// Formats the MAC address as a colon-separated hexadecimal string.
///
/// The format follows the standard MAC address notation with lowercase
/// hexadecimal digits separated by colons (e.g., "00:11:22:33:44:55").
///
/// # Returns
/// A string representation in the format "xx:xx:xx:xx:xx:xx"
///
/// # Examples
/// ```moonbit nocheck
/// let addr = @ipaddr.mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = addr.format() // "00:11:22:33:44:55"
///
/// let _ = @ipaddr.mac(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)
/// let broadcast = @ipaddr.mac(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)
/// let _ = broadcast.format() // "ff:ff:ff:ff:ff:ff"
///
/// let zero_mac = @ipaddr.mac(0x00, 0x00, 0x00, 0x00, 0x00, 0x00)
/// let _ = zero_mac.format() // "00:00:00:00:00:00"
/// ```
///
pub fn Mac::format(self : Self) -> String {
let h0 = format_hex8(self.0.to_int())
let h1 = format_hex8(self.1.to_int())
let h2 = format_hex8(self.2.to_int())
let h3 = format_hex8(self.3.to_int())
let h4 = format_hex8(self.4.to_int())
let h5 = format_hex8(self.5.to_int())
"\{h0}:\{h1}:\{h2}:\{h3}:\{h4}:\{h5}"
}
///|
/// Checks if the MAC address is the broadcast address.
///
/// The broadcast MAC address is ff:ff:ff:ff:ff:ff, which is used to send
/// frames to all devices on the local network segment.
///
/// # Returns
/// `true` if the address is ff:ff:ff:ff:ff:ff, `false` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// let broadcast = @ipaddr.mac(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)
/// let _ = broadcast.is_broadcast() // true
///
/// let normal_mac = @ipaddr.mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = normal_mac.is_broadcast() // false
///
/// let almost_broadcast = @ipaddr.mac(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE)
/// let _ = almost_broadcast.is_broadcast() // false
/// ```
///
pub fn Mac::is_broadcast(self : Self) -> Bool {
self.0 == 0xFF &&
self.1 == 0xFF &&
self.2 == 0xFF &&
self.3 == 0xFF &&
self.4 == 0xFF &&
self.5 == 0xFF
}
///|
/// Checks if the MAC address is a multicast address.
///
/// A MAC address is multicast if the least significant bit of the first octet
/// is set to 1. Multicast addresses are used to send frames to a group of
/// devices rather than a single device.
///
/// # Returns
/// `true` if the address is multicast, `false` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// // Multicast address (first bit of 0x01 is 1)
/// let _ = @ipaddr.mac(0x01, 0x00, 0x5E, 0x00, 0x00, 0x01)
/// let multicast = @ipaddr.mac(0x01, 0x00, 0x5E, 0x00, 0x00, 0x01)
/// let _ = multicast.is_multicast() // true
///
/// // Broadcast is also multicast
/// let _ = @ipaddr.mac(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)
/// let broadcast = @ipaddr.mac(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF)
/// let _ = broadcast.is_multicast() // true
///
/// // Unicast address (first bit of 0x00 is 0)
/// let unicast = @ipaddr.mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = unicast.is_multicast() // false
/// ```
///
pub fn Mac::is_multicast(self : Self) -> Bool {
(self.0.to_int() & 0x01) != 0
}
/// Checks if the MAC address is a unicast address.
///
/// A MAC address is unicast if the least significant bit of the first octet
/// is set to 0. Unicast addresses are used to send frames to a single specific
/// device on the network.
///
/// # Returns
/// `true` if the address is unicast, `false` otherwise
///
/// # Examples
/// ```moonbit
/// // Unicast address (first bit of 0x00 is 0)
/// let unicast = @ipaddr.mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = unicast = unicast.is_unicast() // true
///
/// // Another unicast address (first bit of 0x02 is 0)
/// let unicast2 = @ipaddr.mac(0x02, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = also_unicast = unicast2.is_unicast() // true
///
/// // Multicast address (first bit of 0x01 is 1)
/// let _ = @ipaddr.mac(0x01, 0x00, 0x5E, 0x00, 0x00, 0x01)
/// let _ = not_unicast = multicast.is_unicast() // false
/// ```
///|
pub fn Mac::is_unicast(self : Self) -> Bool {
(self.0.to_int() & 0x01) == 0
}
///|
/// Checks if the MAC address is locally administered.
///
/// A MAC address is locally administered if the second least significant bit
/// of the first octet is set to 1. Locally administered addresses are assigned
/// by the local network administrator rather than by the manufacturer.
///
/// # Returns
/// `true` if the address is locally administered, `false` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// // Locally administered address (second bit of 0x02 is 1)
/// let local_mac = @ipaddr.mac(0x02, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = local_mac.is_local() // true
///
/// // Another locally administered address (0x03 = 0b00000011)
/// let local_mac2 = @ipaddr.mac(0x03, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = local_mac2.is_local() // true
///
/// // Globally unique address (second bit of 0x00 is 0)
/// let global_mac = @ipaddr.mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = global_mac.is_local() // false
/// ```
///
pub fn Mac::is_local(self : Self) -> Bool {
(self.0.to_int() & 0x02) != 0
}
///|
/// Checks if the MAC address is globally unique.
///
/// A MAC address is globally unique if the second least significant bit
/// of the first octet is set to 0. Globally unique addresses are assigned
/// by the manufacturer and should be unique worldwide.
///
/// # Returns
/// `true` if the address is globally unique, `false` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// // Globally unique address (second bit of 0x00 is 0)
/// let global_mac = @ipaddr.mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = global_mac.is_global() // true
///
/// // Another globally unique address (0x01 = 0b00000001, second bit is 0)
/// let global_mac2 = @ipaddr.mac(0x01, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = global_mac2.is_global() // true
///
/// // Locally administered address (second bit of 0x02 is 1)
/// let local_mac = @ipaddr.mac(0x02, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = local_mac.is_global() // false
/// ```
///
pub fn Mac::is_global(self : Self) -> Bool {
(self.0.to_int() & 0x02) == 0
}
///|
/// Gets the Organizationally Unique Identifier (OUI) from the MAC address.
///
/// The OUI is the first three bytes of a MAC address, assigned by the IEEE
/// to identify the manufacturer or organization. It uniquely identifies the
/// vendor of the network interface.
///
/// # Returns
/// A tuple containing the three OUI bytes (b0, b1, b2)
///
/// # Examples
/// ```moonbit nocheck
/// let addr = @ipaddr.mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let (_, _, _) = addr.oui() // (0x00, 0x11, 0x22)
///
/// // Apple's OUI is 00:17:F2
/// let apple_mac = @ipaddr.mac(0x00, 0x17, 0xF2, 0x12, 0x34, 0x56)
/// let (_, _, _) = apple_mac.oui() // (0x00, 0x17, 0xF2)
///
/// // Intel's OUI is 00:15:17
/// let intel_mac = @ipaddr.mac(0x00, 0x15, 0x17, 0xAB, 0xCD, 0xEF)
/// let (_, _, _) = intel_mac.oui() // (0x00, 0x15, 0x17)
/// ```
///
pub fn Mac::oui(self : Self) -> (Int, Int, Int) {
(self.0.to_int(), self.1.to_int(), self.2.to_int())
}
///|
/// Converts the MAC address to a 32-bit integer using the lower 4 bytes.
///
/// This conversion uses only the last 4 bytes (b2, b3, b4, b5) of the MAC address
/// for simplicity, as full 48-bit integers are not commonly used. The first two
/// bytes (b0, b1) are ignored in this conversion.
///
/// # Returns
/// A 32-bit integer representation of the lower 4 bytes
///
/// # Examples
/// ```moonbit nocheck
/// let addr = @ipaddr.mac(0x00, 0x11, 0x22, 0x33, 0x44, 0x55)
/// let _ = addr.to_int() // Uses bytes 0x22, 0x33, 0x44, 0x55
///
/// let simple_mac = @ipaddr.mac(0x00, 0x00, 0x00, 0x00, 0x00, 0x01)
/// let _ = simple_mac.to_int() // 1
///
/// let zero_mac = @ipaddr.mac(0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00)
/// let _ = zero_mac.to_int() // 0 (ignores first two 0xFF bytes)
/// ```
///
pub fn Mac::to_int(self : Self) -> Int {
(self.2.to_int() << 24) |
(self.3.to_int() << 16) |
(self.4.to_int() << 8) |
self.5.to_int()
}
///|
/// Creates a MAC address from a 32-bit integer, setting only the lower 4 bytes.
///
/// This conversion creates a MAC address where the first two bytes (b0, b1) are
/// set to zero, and the remaining four bytes are derived from the 32-bit integer.
/// This is useful for creating MAC addresses from simple integer identifiers.
///
/// # Parameters
/// - `n`: A 32-bit integer to convert to MAC address
///
/// # Returns
/// A MAC address with the first two bytes as 0x00 and the last four bytes from the integer
///
/// # Examples
/// ```moonbit nocheck
/// let mac_from_1 = @ipaddr.Mac::from_int(1) // 00:00:00:00:00:01
/// let _ = mac_from_1.format() // "00:00:00:00:00:01"
///
/// let mac_from_large = @ipaddr.Mac::from_int(0x12345678) // 00:00:12:34:56:78
/// let _ = mac_from_large.format() // "00:00:12:34:56:78"
///
/// // Round trip conversion
/// let original = @ipaddr.mac(0x00, 0x00, 0x22, 0x33, 0x44, 0x55)
/// let int_val = original.to_int()
/// let _ = @ipaddr.Mac::from_int(int_val) // Same as original
/// ```
///
pub fn Mac::from_int(n : Int) -> Mac {
let b0 = (0).to_byte()
let b1 = (0).to_byte()
let b2 = ((n >> 24) & 0xFF).to_byte()
let b3 = ((n >> 16) & 0xFF).to_byte()
let b4 = ((n >> 8) & 0xFF).to_byte()
let b5 = (n & 0xFF).to_byte()
mac(b0, b1, b2, b3, b4, b5)
}
///|
/// Creates a MAC address from a 48-bit integer with validation.
///
/// This is a safe version of `from_int` that validates the input range.
/// Unlike `from_int`, this function returns `None` for negative values
/// or values that exceed the valid 48-bit unsigned range.
///
/// # Parameters
/// - `n`: A 48-bit integer (0 to 281474976710655)
///
/// # Returns
/// `Some(Mac)` if the integer is valid, `None` otherwise
///
/// # Examples
/// ```moonbit nocheck
/// let _ = @ipaddr.Mac::try_from_int(0x22334455) // Some(00:00:22:33:44:55)
/// let _ = @ipaddr.Mac::try_from_int(0) // Some(00:00:00:00:00:00)
/// let _ = @ipaddr.Mac::try_from_int(-1) // None - negative
/// ```
///
pub fn Mac::try_from_int(n : Int) -> Mac? {
// Check for negative values only - MAC addresses use lower 32 bits in our implementation
if n < 0 {
return None
}
Some(Mac::from_int(n))
}
///|
pub impl Show for IpAddrError with output(self, logger) {
logger.write_string(@debug.render(self.to_repr(), max_depth=2147483647))
}
///|
pub impl Show for Ipv4 with output(self, logger) {
logger.write_string(@debug.render(self.to_repr(), max_depth=2147483647))
}
///|
pub impl Show for Ipv4Prefix with output(self, logger) {
logger.write_string(@debug.render(self.to_repr(), max_depth=2147483647))
}
///|
pub impl Show for Mac with output(self, logger) {
logger.write_string(@debug.render(self.to_repr(), max_depth=2147483647))
}