/// 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.
///|
priv enum AddrFamily {
IPv4 = 4
IPv6 = 6
}
///|
struct Addr(Bytes) derive(Eq, Compare, Hash)
///|
#cfg(target="native")
extern "C" fn get_ipv4_addr_size() -> Int = "moonbitlang_async_ipv4_addr_size"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
fn get_ipv4_addr_size() -> Int = "moonbitlang/async" "socket/ipv4_addr_size"
///|
let ipv4_addr_size : Int = get_ipv4_addr_size()
///|
#cfg(target="native")
extern "C" fn get_ipv6_addr_size() -> Int = "moonbitlang_async_ipv6_addr_size"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
fn get_ipv6_addr_size() -> Int = "moonbitlang/async" "socket/ipv6_addr_size"
///|
let ipv6_addr_size : Int = get_ipv6_addr_size()
///|
#cfg(target="native")
#borrow(addr)
extern "C" fn init_ip_addr(addr : Bytes, ip : UInt, port : Int) -> Unit = "moonbitlang_async_init_ip_addr"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
#borrow(addr)
fn init_ip_addr(addr : Bytes, ip : UInt, port : Int) -> Unit = "moonbitlang/async" "socket/init_ip_addr"
///|
pub fn Addr::new(ip : UInt, port : Int) -> Addr {
let addr = Bytes::make(ipv4_addr_size, 0)
init_ip_addr(addr, ip, port)
Addr(addr)
}
///|
#cfg(target="native")
#borrow(addr, ip)
extern "C" fn init_ipv6_addr(
addr : Bytes,
ip : FixedArray[Byte],
port : Int,
scope_id~ : UInt,
) -> Unit = "moonbitlang_async_init_ipv6_addr"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
#borrow(addr, ip)
fn init_ipv6_addr(
addr : Bytes,
ip : FixedArray[Byte],
port : Int,
scope_id~ : UInt,
) -> Unit = "moonbitlang/async" "socket/init_ipv6_addr"
///|
fn Addr::new_ipv6(ip : FixedArray[Byte], port : Int, scope_id~ : UInt) -> Addr {
let addr = Bytes::make(ipv6_addr_size, 0)
init_ipv6_addr(addr, ip, port, scope_id~)
Addr(addr)
}
///|
let empty_ipv6_bytes : FixedArray[Byte] = FixedArray::make(16, 0)
///|
fn Addr::empty(family : AddrFamily) -> Addr {
match family {
IPv4 => {
let addr = Bytes::make(ipv4_addr_size, 0)
init_ip_addr(addr, 0, 0)
Addr(addr)
}
IPv6 => {
let addr = Bytes::make(ipv6_addr_size, 0)
init_ipv6_addr(addr, empty_ipv6_bytes, 0, scope_id=0)
Addr(addr)
}
}
}
///|
#cfg(target="native")
#borrow(addr)
pub extern "C" fn Addr::ip(addr : Addr) -> UInt = "moonbitlang_async_ip_addr_get_ip"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
#borrow(addr)
fn ip_addr_get_ip(addr : Addr, addr_len? : Int = addr.0.length()) -> UInt = "moonbitlang/async" "socket/ip_addr_get_ip"
///|
#cfg(target="wasm")
pub fn Addr::ip(addr : Addr) -> UInt {
ip_addr_get_ip(addr)
}
///|
#cfg(target="native")
#borrow(addr)
pub extern "C" fn Addr::port(addr : Addr) -> Int = "moonbitlang_async_ip_addr_get_port"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
#borrow(addr)
fn ip_addr_get_port(addr : Addr, addr_len? : Int = addr.0.length()) -> Int = "moonbitlang/async" "socket/ip_addr_get_port"
///|
#cfg(target="wasm")
pub fn Addr::port(addr : Addr) -> Int {
ip_addr_get_port(addr)
}
///|
#cfg(target="native")
#borrow(addr)
pub extern "C" fn Addr::is_ipv6(addr : Addr) -> Bool = "moonbitlang_async_addr_is_ipv6"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
#borrow(addr)
fn addr_is_ipv6(addr : Addr, addr_len? : Int = addr.0.length()) -> Bool = "moonbitlang/async" "socket/addr_is_ipv6"
///|
#cfg(target="wasm")
pub fn Addr::is_ipv6(addr : Addr) -> Bool {
addr_is_ipv6(addr)
}
///|
#cfg(target="native")
#borrow(addr)
pub extern "C" fn Addr::is_multicast(addr : Addr) -> Bool = "moonbitlang_async_addr_is_multicast"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
#borrow(addr)
fn addr_is_multicast(addr : Addr, addr_len? : Int = addr.0.length()) -> Bool = "moonbitlang/async" "socket/addr_is_multicast"
///|
#cfg(target="wasm")
pub fn Addr::is_multicast(addr : Addr) -> Bool {
addr_is_multicast(addr)
}
///|
fn Addr::family(self : Addr) -> AddrFamily {
if self.is_ipv6() {
IPv6
} else {
IPv4
}
}
///|
let ipv4_any : Lazy[Addr] = Lazy(() => Addr::new(0, 0))
///|
let ipv6_any : Lazy[Addr] = Lazy(() => {
Addr::new_ipv6(FixedArray::make(16, 0), 0, scope_id=0)
})
///|
fn Addr::any(family : AddrFamily) -> Addr {
match family {
IPv4 => ipv4_any.force()
IPv6 => ipv6_any.force()
}
}
///|
#cfg(target="native")
extern "C" fn Addr::get_ipv6_bytes_offset() -> Int = "moonbitlang_async_addr_get_ipv6_bytes_offset"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
fn Addr::get_ipv6_bytes_offset() -> Int = "moonbitlang/async" "socket/addr_get_ipv6_bytes_offset"
///|
/// The offset of IPv6 bytes in `struct sockaddr_in6`
let ipv6_bytes_offset : Int = Addr::get_ipv6_bytes_offset()
///|
#cfg(target="native")
#borrow(addr)
extern "C" fn Addr::scope_id(addr : Addr) -> UInt = "moonbitlang_async_addr_get_ipv6_scope_id"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
#borrow(addr)
fn Addr::scope_id(addr : Addr, addr_len? : Int = addr.0.length()) -> UInt = "moonbitlang/async" "socket/addr_get_ipv6_scope_id"
///|
#cfg(target="native")
#borrow(addr)
extern "C" fn Addr::is_ipv6_wildcard(addr : Addr) -> Bool = "moonbitlang_async_addr_is_ipv6_wildcard"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
#borrow(addr)
fn Addr::is_ipv6_wildcard(
addr : Addr,
addr_len? : Int = addr.0.length(),
) -> Bool = "moonbitlang/async" "socket/addr_is_ipv6_wildcard"
///|
pub impl Show for Addr with fn output(self, logger) {
if self.is_ipv6() {
// IPv6 address format
logger <+ "["
write_ipv6_str(self.0[ipv6_bytes_offset:], logger)
let scope_id = self.scope_id()
if scope_id > 0 {
let interface = if_indextoname(scope_id, context="") catch {
_ => scope_id.to_string()
}
logger <+ "%\{interface}"
}
logger <+ "]:\{self.port()}"
} else {
// IPv4 address format
let ip = self.ip()
let port = self.port()
logger <+
"\{ip >> 24}.\{(ip >> 16) & 255}.\{(ip >> 8) & 255}.\{ip & 255}:\{port}"
}
}
///|
pub impl Debug for Addr with fn to_repr(self) {
Repr::opaque_("Addr", Repr::literal(self.to_string()))
}
///|
pub suberror InvalidAddr derive(Debug, ToJson)
///|
/// Parse a string into IPv4 or IPv6 address, format should be `ip:port` for IPv4 or `[ip]:port` for IPv6
// TODO: faster implementation
pub fn Addr::parse(src : String) -> Addr raise {
if src.has_prefix("[") {
let (ip_bytes, port, interface) = try_parse_ipv6(src)
let scope_id = if interface is Some(interface) {
parse_ipv6_zone_suffix(interface, context="@socket.Addr::parse()")
} else {
0
}
Addr::new_ipv6(ip_bytes, port, scope_id~)
} else {
// IPv4 format: ip:port
let (ip, port) = try_parse_ipv4(src) catch { _ => raise InvalidAddr }
guard 0 <= port && port < 65536 else { raise InvalidAddr }
Addr::new(ip, port)
}
}
///|
using @event_loop {type AddrInfo}
///|
#cfg(target="native")
extern "C" fn AddrInfo::is_null(self : AddrInfo) -> Bool = "moonbitlang_async_addrinfo_is_null"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
fn AddrInfo::is_null(self : AddrInfo) -> Bool = "moonbitlang/async" "socket/addrinfo_is_null"
///|
/// Convert AddrInfo to Addr(support both IPv4 and IPv6)
#cfg(target="native")
extern "C" fn AddrInfo::addr_size(self : AddrInfo) -> Int = "moonbitlang_async_addrinfo_addr_size"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
fn AddrInfo::addr_size(self : AddrInfo) -> Int = "moonbitlang/async" "socket/addrinfo_addr_size"
///|
#cfg(target="native")
#borrow(out)
extern "C" fn AddrInfo::fill_addr(
self : AddrInfo,
out : Bytes,
port : Int,
) -> Unit = "moonbitlang_async_addrinfo_fill_addr"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
#borrow(out)
fn AddrInfo::fill_addr(
self : AddrInfo,
out : Bytes,
port : Int,
out_len? : Int = out.length(),
) -> Unit = "moonbitlang/async" "socket/addrinfo_fill_addr"
///|
fn AddrInfo::to_addr(self : AddrInfo, port : Int) -> Addr {
let addr = Bytes::make(self.addr_size(), 0)
self.fill_addr(addr, port)
Addr(addr)
}
///|
#cfg(target="native")
extern "C" fn AddrInfo::next(self : AddrInfo) -> AddrInfo = "moonbitlang_async_addrinfo_get_next"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
fn AddrInfo::next(self : AddrInfo) -> AddrInfo = "moonbitlang/async" "socket/addrinfo_get_next"
///|
#cfg(not(any(platform="windows", target="wasm")))
extern "C" fn AddrInfo::free(self : AddrInfo) = "freeaddrinfo"
///|
#cfg(platform="windows")
extern "C" fn AddrInfo::free(self : AddrInfo) = "moonbitlang_async_freeaddrinfo"
///|
#cfg(target="wasm")
#unsafe_skip_stub_check
fn AddrInfo::free(self : AddrInfo) -> Unit = "moonbitlang/async" "socket/addrinfo_free"
///|
/// Error message from failure in resolving a network hostname.
/// The content of the error message is platfrom-dependent,
/// so users should never depend on it.
pub suberror ResolveHostnameError {
ResolveHostnameError(String)
} derive(Debug, ToJson)
///|
pub(all) enum IpProtocolPreference {
OnlyV4
OnlyV6
FavorV4
FavorV6
NoPreference
} derive(Debug)
///|
/// Resolve a IPv4 or IPv6 address by hostname.
///
/// By default, `Addr::resolve` return the first available address.
/// Preference on IPv4 v.s. IPv6 can be configured via `protocol`.
/// See `IPProtocolPreference` for available options.
pub async fn Addr::resolve(
host : String,
port~ : Int,
protocol? : IpProtocolPreference = NoPreference,
) -> Addr {
// TODO: Add option to prefer ipv6 or ipv4
let context = "@socket.Addr::resolve()"
let ai_root = match @event_loop.getaddrinfo(host, context~) {
Ok(ai_root) => ai_root
Err(msg) => raise ResolveHostnameError(msg)
}
defer ai_root.free()
let first_addr = ai_root.to_addr(port)
// search for address with preferred protocol
match protocol {
FavorV4 | OnlyV4 => {
if !first_addr.is_ipv6() {
return first_addr
}
for ai = ai_root.next(); !ai.is_null(); ai = ai.next() {
let addr = ai.to_addr(port)
if !addr.is_ipv6() {
return addr
}
}
if protocol is OnlyV4 {
raise ResolveHostnameError("No available IPv4 address")
} else {
first_addr
}
}
FavorV6 | OnlyV6 => {
if first_addr.is_ipv6() {
return first_addr
}
for ai = ai_root.next(); !ai.is_null(); ai = ai.next() {
let addr = ai.to_addr(port)
if addr.is_ipv6() {
return addr
}
}
if protocol is OnlyV6 {
raise ResolveHostnameError("No available IPv6 address")
} else {
first_addr
}
}
NoPreference => first_addr
}
}