diff --git a/crates/lb/src/net.rs b/crates/lb/src/net.rs deleted file mode 100644 index 3948187..0000000 --- a/crates/lb/src/net.rs +++ /dev/null @@ -1,1078 +0,0 @@ -//! Network library. -//! -//! The `lb:net` library provides an asynchronous network API for woring with TCP, UDP and IPC -//! sockets. -//! -//! ## Exports -//! -//! See [`lb_netlib`] for items exported by this library. -use derive_more::{From, FromStr}; -use luaffi::{ - cdef, - marker::{OneOf, fun}, - metatype, -}; -use luajit::LUA_NOREF; -use std::io::ErrorKind; -use std::{ - cell::{BorrowError, BorrowMutError, Ref, RefCell, RefMut}, - ffi::c_int, - net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, - time::Duration, -}; -use thiserror::Error; -use tokio::{ - io::{AsyncReadExt, AsyncWriteExt, Interest}, - net::{TcpListener, TcpSocket, TcpStream}, -}; - -/// Errors that can be thrown by this library. -/// -/// Functions which return this error will **throw**. The error message can be caught by using -/// [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall). -#[derive(Debug, Error)] -pub enum Error { - /// Attempt to access an object while it is being modified. - #[error("cannot access object while it is being modified")] - Borrow(#[from] BorrowError), - /// Attempt to modify an object while it is in use. - #[error("cannot modify object while it is in use")] - BorrowMut(#[from] BorrowMutError), - /// I/O error. - #[error("{0}")] - Io(#[from] std::io::Error), - /// IP or socket address syntax error. - #[error("{0}")] - InvalidAddr(#[from] AddrParseError), - /// Socket was already converted and can no longer be used. - #[error("socket was already converted")] - SocketConsumed, -} - -type Result = std::result::Result; - -/// Items exported by the `lb:net` library. -/// -/// This library can be acquired by calling -/// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require). -/// -/// ```lua -/// local net = require("lb:net"); -/// ``` -#[cdef(module = "lb:net")] -pub struct lb_netlib; - -#[metatype] -impl lb_netlib { - #[new] - extern "Lua-C" fn new() -> Self { - Self - } - - /// An IPv4 address representing localhost: `127.0.0.1` - pub extern "Lua-C" fn localhost() -> lb_ipaddr { - lb_ipaddr(Ipv4Addr::LOCALHOST.into()) - } - - /// An IPv6 address representing localhost: `::1` - pub extern "Lua-C" fn localhost_v6() -> lb_ipaddr { - lb_ipaddr(Ipv6Addr::LOCALHOST.into()) - } - - /// An IPv4 address representing an unspecified address: `0.0.0.0` - pub extern "Lua-C" fn unspecified() -> lb_ipaddr { - lb_ipaddr(Ipv4Addr::UNSPECIFIED.into()) - } - - /// An IPv6 address representing an unspecified address: `::` - pub extern "Lua-C" fn unspecified_v6() -> lb_ipaddr { - lb_ipaddr(Ipv6Addr::UNSPECIFIED.into()) - } - - /// An IPv4 address representing the broadcast address: `255.255.255.255` - pub extern "Lua-C" fn broadcast() -> lb_ipaddr { - lb_ipaddr(Ipv4Addr::BROADCAST.into()) - } - - /// Creates an IP address from the given input. - /// - /// If `addr` is an [`lb_ipaddr`], a copy of that value is returned. If `addr` is an - /// [`lb_socketaddr`], the IP part of the socket address is returned. Otherwise, parses `addr` - /// as an IP address string. Both IPv4 and IPv6 addresses are accepted. - /// - /// An address string may be something like `127.0.0.1`. - /// - /// The type of the parsed address can be checked using [`is_v4`](lb_ipaddr::is_v4) or - /// [`is_v6`](lb_ipaddr::is_v6) functions on the returned [`lb_ipaddr`](lb_ipaddr) value. - /// - /// # Errors - /// - /// This function will throw an error if the input syntax is invalid. - /// - /// # Example - /// - /// ```lua - /// local net = require("lb:net") - /// local addr = net.ipaddr("192.168.1.1") - /// - /// assert(addr:is_v4()) - /// ``` - pub extern "Lua" fn ipaddr( - addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, - ) -> Result { - if __istype(__ct.lb_ipaddr, addr) { - __new(__ct.lb_ipaddr, addr) // copy constructor - } else if __istype(__ct.lb_socketaddr, addr) { - s.ip() - } else { - Self::__parse_ipaddr(addr) - } - } - - extern "Lua-C" fn __parse_ipaddr(addr: &str) -> Result { - Ok(addr.parse()?) - } - - /// Creates a socket address from the given input. - /// - /// A socket address is an IP address with a prescribed port number. - /// - /// If `addr` is an [`lb_socketaddr`], a copy of that value is returned. If `addr` is an - /// [`lb_ipaddr`], a socket address with that IP part is returned. Otherwise, parses `addr` as a - /// socket address string. Both IPv4 and IPv6 addresses are accepted. - /// - /// If `port` is specified, it is always used as the port part of the returned socket address. - /// Otherwise, `0` is used as the default. - /// - /// An address string may be something like `127.0.0.1:3000`. - /// - /// # Errors - /// - /// This function will throw an error if the input syntax is invalid. - /// - /// # Example - /// - /// ```lua - /// local net = require("lb:net") - /// local addr = net.socketaddr("::1", 8080) - /// - /// assert(addr:ip():is_v6()) - /// assert(addr:port() == 8080) - /// assert(addr:is_loopback()) - /// ``` - pub extern "Lua" fn socketaddr( - addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, - port: Option, - ) -> Result { - if port != () { - Self::__new_skaddr(Self::ipaddr(addr), port) - } else { - if __istype(__ct.lb_socketaddr, addr) { - __new(__ct.lb_socketaddr, addr) // copy constructor - } else if __istype(__ct.lb_ipaddr, addr) { - Self::__new_skaddr(addr, 0) // default port 0 - } else { - Self::__parse_skaddr(addr) - } - } - } - - extern "Lua-C" fn __new_skaddr(ip: &lb_ipaddr, port: u16) -> lb_socketaddr { - SocketAddr::new(ip.0, port).into() - } - - extern "Lua-C" fn __parse_skaddr(addr: &str) -> Result { - Ok(if let Ok(addr) = addr.parse() { - SocketAddr::new(addr, 0).into() // default port 0 - } else { - addr.parse::()?.into() - }) - } - - /// Creates a new TCP socket configured for IPv4. - /// - /// This calls `socket(2)` with `AF_INET` and `SOCK_STREAM`. - /// - /// # Errors - /// - /// This function may throw an error if the socket could not be created. - pub extern "Lua-C" fn tcp() -> Result { - Ok(lb_tcpsocket::new(TcpSocket::new_v4()?)) - } - - /// Creates a new TCP socket configured for IPv6. - /// - /// This calls `socket(2)` with `AF_INET6` and `SOCK_STREAM`. - /// - /// # Errors - /// - /// This function may throw an error if the socket could not be created. - pub extern "Lua-C" fn tcp_v6() -> Result { - Ok(lb_tcpsocket::new(TcpSocket::new_v6()?)) - } - - /// Creates a new TCP socket bound to the given address and port. - /// - /// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a - /// new TCP socket configured to use either IPv4 or IPv6 depending on the type of the given - /// address, and binds it to that address. - /// - /// # Errors - /// - /// This function may throw an error if the socket could not be created or bound to the - /// specified address. - /// - /// # Example - /// - /// ```lua - /// local net = require("lb:net") - /// local socket = net.bind_tcp("127.0.0.1", 8080) - /// - /// assert(socket:local_addr():ip():is_loopback()) - /// assert(socket:local_addr():port() == 8080) - /// ``` - pub async extern "Lua" fn bind_tcp( - addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, - port: Option, - ) -> Result { - let addr = Self::socketaddr(addr, port); - let socket; - if addr.ip().is_v6() { - socket = Self::tcp_v6(); - } else { - socket = Self::tcp(); - } - socket.bind(addr); - socket - } - - /// Creates a new TCP socket listening on the given address and port. - /// - /// This is a convenience function that combines [`bind_tcp`](Self::bind_tcp) and - /// [`listen`](lb_tcpsocket::listen). It accepts the same arguments as - /// [`socketaddr`](Self::socketaddr). - /// - /// # Errors - /// - /// This function may throw an error if the socket could not be created, bound to the specified - /// address, or could not transition to the listening state. - pub async extern "Lua" fn listen_tcp( - addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, - port: Option, - ) -> Result { - Self::bind_tcp(addr, port).listen(1024) - } - - /// Establishes a new TCP connection to the server at the given address and port. - /// - /// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a - /// new TCP socket and connects it to the specified address. - /// - /// # Errors - /// - /// This function may throw an error if the socket could not be created or connected to the - /// specified address. - pub async extern "Lua" fn connect_tcp( - addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, - port: Option, - ) -> Result { - let addr = Self::socketaddr(addr, port); - let socket; - if addr.ip().is_v6() { - socket = Self::tcp_v6(); - } else { - socket = Self::tcp(); - } - socket.connect(addr) - } -} - -/// IP address, either IPv4 or IPv6. -/// -/// # Example -/// -/// This example creates an [`lb_ipaddr`] by parsing an IP address string. -/// -/// ```lua -/// local net = require("lb:net"); -/// local addr = net.ipaddr("127.0.0.1"); -- ipv4 loopback address -/// -/// assert(addr:is_v4()); -/// assert(addr:is_loopback()); -/// ``` -#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)] -#[cdef] -pub struct lb_ipaddr(#[opaque] IpAddr); - -#[metatype] -impl lb_ipaddr { - /// Returns `true` if this is an IPv4 address. - pub extern "Lua-C" fn is_v4(&self) -> bool { - self.0.is_ipv4() - } - - /// Returns `true` if this is an IPv6 address. - pub extern "Lua-C" fn is_v6(&self) -> bool { - self.0.is_ipv6() - } - - /// Returns the string `"v4"` if this is an IPv4 address, or `"v6"` if this is an IPv6 address. - pub extern "Lua" fn family(&self) -> String { - if self.is_v6() { "v6" } else { "v4" } - } - - /// Returns `true` if this is a loopback address. - /// - /// For IPv4, this is any address in the `127.0.0.0/8` range. - /// - /// For IPv6, this is the address `::1`. - pub extern "Lua-C" fn is_loopback(&self) -> bool { - self.0.is_loopback() - } - - /// Returns `true` if this address is unspecified. - /// - /// For IPv4, this is the address `0.0.0.0`. - /// - /// For IPv6, this is the address `::`. - pub extern "Lua-C" fn is_unspecified(&self) -> bool { - self.0.is_unspecified() - } - - /// Returns `true` if this address is a multicast address. - /// - /// For IPv4, this is any address in the `224.0.0.0/4` range. - /// - /// For IPv6, this is any address in the `ff00::/8` range. - pub extern "Lua-C" fn is_multicast(&self) -> bool { - self.0.is_multicast() - } - - /// Returns `true` if this is an IPv4 private address. - /// - /// For IPv4, this is any address in one of these ranges: - /// - /// - `10.0.0.0/8` - /// - `172.16.0.0/12` - /// - `192.168.0.0/16` - /// - /// For IPv6, this always returns `false`. - pub extern "Lua-C" fn is_v4_private(&self) -> bool { - match self.0 { - IpAddr::V4(v4) => v4.is_private(), - IpAddr::V6(_) => false, - } - } - - /// Returns `true` if this is an IPv4 link-local address. - /// - /// For IPv4, this is any address in the `169.254.0.0/16` range. - /// - /// For IPv6, this always returns `false`. - pub extern "Lua-C" fn is_v4_link_local(&self) -> bool { - match self.0 { - IpAddr::V4(v4) => v4.is_link_local(), - IpAddr::V6(_) => false, - } - } - - /// Returns `true` if this is an IPv4 broadcast address. - /// - /// For IPv4, this is the address `255.255.255.255`. - /// - /// For IPv6, this always returns `false`. - pub extern "Lua-C" fn is_v4_broadcast(&self) -> bool { - match self.0 { - IpAddr::V4(v4) => v4.is_broadcast(), - IpAddr::V6(_) => false, - } - } - - /// Returns `true` if this is an IPv4 documentation address. - /// - /// For IPv4, this is any address in one of these ranges: - /// - /// - `192.0.2.0/24` (TEST-NET-1) - /// - `198.51.100.0/24` (TEST-NET-2) - /// - `203.0.113.0/24` (TEST-NET-3) - /// - /// For IPv6, this always returns `false`. - pub extern "Lua-C" fn is_v4_documentation(&self) -> bool { - match self.0 { - IpAddr::V4(v4) => v4.is_documentation(), - IpAddr::V6(_) => false, - } - } - - /// Returns `true` if this is an IPv6 unique local address. - /// - /// For IPv4, this always returns `false`. - /// - /// For IPv6, this is any address in the `fc00::/7` range. - pub extern "Lua-C" fn is_v6_unique_local(&self) -> bool { - match self.0 { - IpAddr::V4(_) => false, - IpAddr::V6(v6) => v6.is_unique_local(), - } - } - - /// Returns `true` if this is an IPv6 unicast address with link-local scope. - /// - /// For IPv4, this always returns `false`. - /// - /// For IPv6, this is any address in the `fe80::/10` range. - pub extern "Lua-C" fn is_v6_unicast_link_local(&self) -> bool { - match self.0 { - IpAddr::V4(_) => false, - IpAddr::V6(v6) => v6.is_unicast_link_local(), - } - } - - /// Converts this address to IPv4. - /// - /// For IPv4, this returns the address unchanged. - /// - /// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible - /// IPv6 address. Otherwise, this returns `nil`. - pub extern "Lua-C" fn to_v4(&self) -> Option { - match self.0 { - IpAddr::V4(_) => Some(*self), - IpAddr::V6(v6) => v6.to_ipv4().map(|v| Self(v.into())), - } - } - - /// Converts this address to IPv6. - /// - /// For IPv4, this returns the IPv4-mapped IPv6 address as defined by - /// [`Ipv4Addr::to_ipv6_mapped`]. - /// - /// For IPv6, this returns the address unchanged. - pub extern "Lua-C" fn to_v6(&self) -> Self { - match self.0 { - IpAddr::V4(v4) => Self(v4.to_ipv6_mapped().into()), - IpAddr::V6(_) => *self, - } - } - - /// Returns the canonical form of this address. - /// - /// For IPv4, this returns the address unchanged. - /// - /// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible - /// IPv6 address. Otherwise, this returns the address unchanged. - pub extern "Lua-C" fn to_canonical(&self) -> Self { - match self.0 { - IpAddr::V4(_) => *self, - IpAddr::V6(v6) => v6.to_ipv4().map_or(*self, |v| Self(v.into())), - } - } - - /// Returns `true` if the given addresses are equal. - /// - /// Two addresses are considered equal if they are of the same family (IPv4 or IPv6) and - /// represent the same address in octets. - #[eq] - pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool { - left.0 == right.0 - } - - /// Returns `true` if the left address is less than the right address. - /// - /// IPv4 addresses are always less than IPv6 addresses. - #[lt] - pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool { - left.0 < right.0 - } - - /// Returns `true` if the left address is less than or equal to the right address. - /// - /// IPv4 addresses are always less than IPv6 addresses. - #[le] - pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool { - left.0 <= right.0 - } - - /// Returns the string representation of this address. - #[tostring] - pub extern "Lua-C" fn tostring(&self) -> String { - self.0.to_string() - } -} - -/// Socket address, which is an IP address with a port number. -/// -/// This represents an IP address with a prescribed port, such as `127.0.0.1:8080` or `[::1]:443`. -/// It is used to specify endpoints for network connections and listeners, and can be constructed by -/// [`socketaddr`](lb_netlib::socketaddr). -#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)] -#[cdef] -pub struct lb_socketaddr(#[opaque] SocketAddr); - -#[metatype] -impl lb_socketaddr { - /// The IP part of this address. - pub extern "Lua-C" fn ip(&self) -> lb_ipaddr { - self.0.ip().into() - } - - /// The port part of this address. - pub extern "Lua-C" fn port(&self) -> u16 { - self.0.port() - } - - /// Returns a new socket address with the given IP address and the same port. - pub extern "Lua-C" fn with_ip(&self, ip: &lb_ipaddr) -> Self { - SocketAddr::new(ip.0, self.port()).into() - } - - /// Returns a new socket address with the given port and the same IP address. - pub extern "Lua-C" fn with_port(&self, port: u16) -> Self { - SocketAddr::new(self.ip().0, port).into() - } - - /// Returns `true` if the given addresses are equal. - #[eq] - pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool { - left.0 == right.0 - } - - /// Returns `true` if the left address is less than the right address. - #[lt] - pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool { - left.0 < right.0 - } - - /// Returns `true` if the left address is less than or equal to the right address. - #[le] - pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool { - left.0 <= right.0 - } - - /// Returns the string representation of this address. - #[tostring] - pub extern "Lua-C" fn tostring(&self) -> String { - self.0.to_string() - } -} - -/// TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`]. -/// -/// This type represents a TCP socket in its initial state, before it is connected or set to listen. -/// It can be configured (e.g., socket options, bind address) before being converted to an -/// [`lb_tcpstream`] (via [`connect`](lb_tcpsocket::connect)) or [`lb_tcplistener`] (via -/// [`listen`](lb_tcpsocket::listen)), after which it can no longer be used. -#[derive(Debug)] -#[cdef] -pub struct lb_tcpsocket(#[opaque] RefCell>); - -#[metatype] -impl lb_tcpsocket { - fn new(socket: TcpSocket) -> Self { - Self(RefCell::new(Some(socket))) - } - - fn socket<'s>(&'s self) -> Result> { - let socket = self.0.borrow(); - match *socket { - Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())), - None => Err(Error::SocketConsumed), - } - } - - /// Gets the value of the `SO_KEEPALIVE` option on this socket. - pub extern "Lua-C" fn keepalive(&self) -> Result { - Ok(self.socket()?.keepalive()?) - } - - /// Sets value for the `SO_KEEPALIVE` option on this socket. - /// - /// This enables or disables periodic keepalive messages on the connection. - pub extern "Lua-C" fn set_keepalive(&self, enabled: bool) -> Result<()> { - Ok(self.socket()?.set_keepalive(enabled)?) - } - - /// Gets the value of the `SO_REUSEADDR` option on this socket. - pub extern "Lua-C" fn reuseaddr(&self) -> Result { - Ok(self.socket()?.reuseaddr()?) - } - - /// Sets value for the `SO_REUSEADDR` option on this socket. - /// - /// This allows the socket to bind to an address that is already in use. - pub extern "Lua-C" fn set_reuseaddr(&self, enabled: bool) -> Result<()> { - Ok(self.socket()?.set_reuseaddr(enabled)?) - } - - /// Gets the value of the `SO_REUSEPORT` option on this socket. - pub extern "Lua-C" fn reuseport(&self) -> Result { - Ok(self.socket()?.reuseport()?) - } - - /// Sets value for the `SO_REUSEPORT` option on this socket. - /// - /// This allows multiple sockets to bind to the same port. - pub extern "Lua-C" fn set_reuseport(&self, enabled: bool) -> Result<()> { - Ok(self.socket()?.set_reuseport(enabled)?) - } - - /// Gets the value of the `SO_SNDBUF` option on this socket. - pub extern "Lua-C" fn sendbuf(&self) -> Result { - Ok(self.socket()?.send_buffer_size()?) - } - - /// Sets value for the `SO_SNDBUF` option on this socket. - /// - /// This sets the size of the send buffer in bytes. - pub extern "Lua-C" fn set_sendbuf(&self, size: u32) -> Result<()> { - Ok(self.socket()?.set_send_buffer_size(size)?) - } - - /// Gets the value of the `SO_RCVBUF` option on this socket. - pub extern "Lua-C" fn recvbuf(&self) -> Result { - Ok(self.socket()?.recv_buffer_size()?) - } - - /// Sets value for the `SO_RCVBUF` option on this socket. - /// - /// This sets the size of the receive buffer in bytes. - pub extern "Lua-C" fn set_recvbuf(&self, size: u32) -> Result<()> { - Ok(self.socket()?.set_recv_buffer_size(size)?) - } - - /// Gets the value of the `SO_LINGER` option on this socket, in seconds. - pub extern "Lua-C" fn linger(&self) -> Result { - Ok(self - .socket()? - .linger()? - .map(|n| n.as_secs_f64()) - .unwrap_or(0.)) - } - - /// Sets the value of the `SO_LINGER` option on this socket. - /// - /// This controls how long the socket will remain open after close if unsent data is present. - pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> { - let secs = secs.max(0.); - Ok(self - .socket()? - .set_linger((secs != 0.).then_some(Duration::from_secs_f64(secs)))?) - } - - /// Gets the value of the `TCP_NODELAY` option on this socket. - pub extern "Lua-C" fn nodelay(&self) -> Result { - Ok(self.socket()?.nodelay()?) - } - - /// Sets the value of the `TCP_NODELAY` option on this socket. - /// - /// This enables or disables Nagle's algorithm, which delays sending small packets. - pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> { - Ok(self.socket()?.set_nodelay(enabled)?) - } - - /// Gets the local address that this socket is bound to. - pub extern "Lua-C" fn local_addr(&self) -> Result { - Ok(self.socket()?.local_addr()?.into()) - } - - /// Binds this socket to the given local address. - pub extern "Lua-C" fn bind(&self, addr: &lb_socketaddr) -> Result<()> { - Ok(self.socket()?.bind(addr.0)?) - } - - /// Transitions this socket to the listening state. - /// - /// This consumes the socket and returns a new [`lb_tcplistener`] that can accept incoming - /// connections. This socket object can no longer be used after this call. - pub extern "Lua-C" fn listen(&self, backlog: u32) -> Result { - let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?; - Ok(lb_tcplistener::new(socket.listen(backlog)?)) - } - - /// Connects this socket to the given remote socket address, transitioning it to an established - /// state. - /// - /// This consumes the socket and returns a new [`lb_tcpstream`] that can be used to send and - /// receive data. This socket object can no longer be used after this call. - /// - /// # Errors - /// - /// This function may throw an error if connection could not be established to the given remote - /// address. - pub async extern "Lua-C" fn connect(&self, addr: &lb_socketaddr) -> Result { - let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?; - Ok(lb_tcpstream::new(socket.connect(addr.0).await?)) - } -} - -/// TCP connection between a local and a remote socket. -/// -/// This represents an established TCP connection. It is created by connecting an [`lb_tcpsocket`] -/// to a remote socket address (via [`connect`](lb_tcpsocket::connect)) or accepting a connection -/// from an [`lb_tcplistener`] (via [`accept`](lb_tcplistener::accept)). It provides methods for -/// reading from and writing to the stream asynchronously. -/// -/// This type supports reading and writing data in both directions concurrently. Typically you would -/// spawn one reader and one writer task to handle incoming and outgoing data respectively. -/// Connection is closed when this object goes out of scope and gets garbage collected, or when -/// [`shutdown`](Self::shutdown) is explicitly called. -/// -/// # Example -/// -/// This examples spawns a reader task and a writer task to operate on the stream concurrently. -/// -/// ```lua -/// local task = require("lb:task") -/// local net = require("lb:net") -/// local socket = net.connect_tcp("127.0.0.1:1234") -/// -/// print("local address:", socket:local_addr()) -/// print("remote address:", socket:peer_addr()) -/// -/// local reader = spawn(function() -/// for chunk in socket, 1024 do -/// if chunk ~= nil then -/// print("received:", chunk) -/// else -/// print("read half closed") -/// end -/// done -/// end) -/// -/// local writer = spawn(function() -/// for i = 1, 10 do -/// socket:write(("message %d\n"):format(i)) -/// print("sent message", i) -/// done -/// end) -/// -/// task.join(reader, writer) -/// ``` -#[derive(Debug)] -#[cdef] -pub struct lb_tcpstream { - #[opaque] - read: RefCell, - #[opaque] - write: RefCell, -} - -#[metatype] -impl lb_tcpstream { - fn new(stream: TcpStream) -> Self { - let (read, write) = stream.into_split(); - Self { - read: RefCell::new(read), - write: RefCell::new(write), - } - } - - fn read_half<'s>(&'s self) -> Result> { - Ok(self.read.try_borrow_mut()?) - } - - fn write_half<'s>(&'s self) -> Result> { - Ok(self.write.try_borrow_mut()?) - } - - /// The local socket address that this stream is bound to. - pub extern "Lua-C" fn local_addr(&self) -> Result { - Ok(self.read_half()?.local_addr()?.into()) - } - - /// The remote socket address that this stream is connected to. - pub extern "Lua-C" fn peer_addr(&self) -> Result { - Ok(self.read_half()?.peer_addr()?.into()) - } - - /// Waits for this stream to be ready in the given half. - /// - /// The argument `half` can be `"read"` for the readable half, `"write"` for the writable half, - /// or `nil` for both. - pub async extern "Lua-C" fn ready(&self, half: Option<&str>) -> Result<()> { - let ty = match half { - None => Interest::READABLE | Interest::WRITABLE, - Some("read") => Interest::READABLE, - Some("write") => Interest::WRITABLE, - _ => Err(std::io::Error::new( - ErrorKind::InvalidInput, - "invalid ready interest", - ))?, - }; - - self.read_half()?.ready(ty).await?; - Ok(()) - } - - fn is_disc(err: ErrorKind) -> bool { - matches!( - err, - ErrorKind::ConnectionReset - | ErrorKind::BrokenPipe - | ErrorKind::UnexpectedEof - | ErrorKind::WriteZero - ) - } - - /// Reads exactly `len` bytes from this stream. - /// - /// If the connection was closed, this returns `nil`. - pub async extern "Lua-C" fn read(&self, len: u32) -> Result>> { - let mut buf = vec![0; len as usize]; - Ok(match self.read_half()?.read_exact(&mut buf).await { - Ok(_) => Some(buf), - Err(err) if Self::is_disc(err.kind()) => None, - Err(err) => return Err(err.into()), - }) - } - - /// Reads up to `len` bytes from this stream. - /// - /// The returned bytes may be less than `len` in length if the stream had less data available in - /// queue. If the connection was closed, this returns `nil`. - pub async extern "Lua-C" fn read_partial(&self, len: u32) -> Result>> { - let mut buf = vec![0; len as usize]; - Ok(match self.read_half()?.read(&mut buf).await { - Ok(0) => None, - Ok(n) => Some({ - buf.truncate(n); - buf - }), - Err(err) if Self::is_disc(err.kind()) => None, - Err(err) => return Err(err.into()), - }) - } - - /// Attempts to read up to `len` bytes from this stream without waiting. - /// - /// The returned bytes may be less than `len` in length if the stream had less data available in - /// queue. If there was no data available or the connection was closed, this returns `nil`. - pub extern "Lua-C" fn try_read(&self, len: u32) -> Result>> { - let mut buf = vec![0; len as usize]; - Ok(match self.read_half()?.try_read(&mut buf) { - Ok(0) => None, - Ok(n) => Some({ - buf.truncate(n); - buf - }), - Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None, - Err(err) => return Err(err.into()), - }) - } - - /// Writes the given bytes to this stream. - pub async extern "Lua-C" fn write(&self, buf: &[u8]) -> Result { - Ok(match self.write_half()?.write_all(buf).await { - Ok(()) => true, - Err(err) if Self::is_disc(err.kind()) => false, - Err(err) => return Err(err.into()), - }) - } - - /// Writes the given bytes to this stream and returns the number of bytes successfully written. - pub async extern "Lua-C" fn write_partial(&self, buf: &[u8]) -> Result> { - Ok(match self.write_half()?.write(buf).await { - Ok(0) => None, - Ok(n) => Some(n as u32), - Err(err) if Self::is_disc(err.kind()) => None, - Err(err) => return Err(err.into()), - }) - } - - pub extern "Lua-C" fn try_write(&self, buf: &[u8]) -> Result> { - Ok(match self.write_half()?.try_write(buf) { - Ok(n) => Some(n as u32), - Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None, - Err(err) => return Err(err.into()), - }) - } - - /// Peeks up to `len` bytes at incoming data without consuming it. - /// - /// Successive calls will return the same data until it is consumed by the [`read`](Self::read) - /// family of functions. - pub async extern "Lua-C" fn peek(&self, len: u32) -> Result>> { - let mut buf = vec![0; len as usize]; - Ok(match self.read_half()?.peek(&mut buf).await { - Ok(0) => None, - Ok(n) => Some({ - buf.truncate(n); - buf - }), - Err(err) if Self::is_disc(err.kind()) => None, - Err(err) => return Err(err.into()), - }) - } - - /// Shuts down this connection. - pub async extern "Lua-C" fn shutdown(&self) -> Result<()> { - Ok(self.write_half()?.shutdown().await?) - } - - #[call] - pub async extern "Lua" fn call(&self, len: u32) -> Result>> { - self.read_partial(len) - } -} - -/// TCP socket server, listening for connections. -/// -/// This type represents a TCP server socket that can accept incoming connections. It is created by -/// transitioning an [`lb_tcpsocket`] to the listening state via [`listen`](lb_tcpsocket::listen). -#[derive(Debug)] -#[cdef] -pub struct lb_tcplistener { - #[opaque] - listener: TcpListener, - __on_accept_ref: c_int, -} - -#[metatype] -impl lb_tcplistener { - fn new(listener: TcpListener) -> Self { - Self { - listener, - __on_accept_ref: LUA_NOREF, - } - } - - /// The local socket address that this listener is bound to. - pub extern "Lua-C" fn local_addr(&self) -> Result { - Ok(self.listener.local_addr()?.into()) - } - - /// Gets the value of the `IP_TTL` option for this socket. - pub extern "Lua-C" fn ttl(&self) -> Result { - Ok(self.listener.ttl()?) - } - - /// Sets the value for the `IP_TTL` option on this socket. - pub extern "Lua-C" fn set_ttl(&self, ttl: u32) -> Result<()> { - Ok(self.listener.set_ttl(ttl)?) - } - - /// Registers a callback to be invoked with each new incoming connection before it is converted - /// to an [`lb_tcpstream`]. - /// - /// The callback receives a temporary [`lb_tcplistener_stream`] object, which can be used to - /// configure socket options (such as [`set_nodelay`](lb_tcplistener_stream), - /// [`set_linger`](lb_tcplistener_stream), etc.) before the stream is converted to an - /// [`lb_tcpstream`]. The callback is called synchronously during [`accept`](Self::accept) and - /// should complete as quickly as possible. The provided configurable object is only valid - /// within the callback and is converted to an [`lb_tcpstream`] as soon as it returns. - pub extern "Lua" fn on_accept(&self, cb: fun<(&lb_tcplistener_stream,), ()>) { - assert( - rawequal(cb, ()) || r#type(cb) == "function", - concat!("function expected in argument 'cb', got ", r#type(cb)), - ); - __unref(self.__on_accept_ref); - self.__on_accept_ref = __ref(cb); - } - - /// Accepts a new incoming TCP connection. - /// - /// If an [`on_accept`](Self::on_accept) callback is registered, it is invoked with a temporary - /// [`lb_tcplistener_stream`] object representing the new connection. This allows configuration - /// of socket options for this specific connection, before the stream is converted to an - /// [`lb_tcpstream`] and returned for the connection to be read from or written to. - #[call] - pub async extern "Lua" fn accept(&self) -> Result { - let stream = self.__accept(); - let on_accept = __registry[self.__on_accept_ref]; - if on_accept != () { - on_accept(stream); - } - stream.__convert() - } - - async extern "Lua-C" fn __accept(&self) -> Result { - let (stream, _) = self.listener.accept().await?; - Ok(lb_tcplistener_stream::new(stream)) - } - - #[gc] - extern "Lua" fn gc(&self) { - __unref(self.__on_accept_ref); - } -} - -/// TCP connection that has just been accepted by [`lb_tcplistener`]. -/// -/// This type is passed to the [`on_accept`](lb_tcplistener::on_accept) callback on -/// [`lb_tcplistener`], allowing socket options to be set before the stream is converted to an -/// [`lb_tcpstream`]. After conversion, this object can no longer be used. -#[derive(Debug)] -#[cdef] -pub struct lb_tcplistener_stream(#[opaque] RefCell>); - -#[metatype] -impl lb_tcplistener_stream { - fn new(stream: TcpStream) -> Self { - Self(RefCell::new(Some(stream))) - } - - fn stream<'s>(&'s self) -> Result> { - let socket = self.0.borrow(); - match *socket { - Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())), - None => Err(Error::SocketConsumed), - } - } - - /// The local socket address that the listener is bound to. - pub extern "Lua-C" fn local_addr(&self) -> Result { - Ok(self.stream()?.local_addr()?.into()) - } - - /// The remote socket address that this stream is connected to. - pub extern "Lua-C" fn peer_addr(&self) -> Result { - Ok(self.stream()?.peer_addr()?.into()) - } - - /// Gets the value of the `TCP_NODELAY` option on this stream. - pub extern "Lua-C" fn nodelay(&self) -> Result { - Ok(self.stream()?.nodelay()?) - } - - /// Sets the value of the `TCP_NODELAY` option on this stream. - /// - /// This enables or disables Nagle's algorithm, which delays sending small packets. - pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> { - Ok(self.stream()?.set_nodelay(enabled)?) - } - - /// Gets the value of the `SO_LINGER` option on this stream, in seconds. - pub extern "Lua-C" fn linger(&self) -> Result { - Ok(self - .stream()? - .linger()? - .map(|n| n.as_secs_f64()) - .unwrap_or(0.)) - } - - /// Sets the value of the `SO_LINGER` option on this stream. - /// - /// This controls how long the stream will remain open after close if unsent data is present. - pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> { - let secs = secs.max(0.); - Ok(self - .stream()? - .set_linger((secs != 0.).then_some(std::time::Duration::from_secs_f64(secs)))?) - } - - /// Gets the value of the `IP_TTL` option for this stream. - pub extern "Lua-C" fn ttl(&self) -> Result { - Ok(self.stream()?.ttl()?) - } - - /// Sets the value for the `IP_TTL` option on this stream. - pub extern "Lua-C" fn set_ttl(&self, ttl: u32) -> Result<()> { - Ok(self.stream()?.set_ttl(ttl)?) - } - - extern "Lua-C" fn __convert(&self) -> Result { - Ok(lb_tcpstream::new( - self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?, - )) - } -} diff --git a/crates/lb/src/net/mod.rs b/crates/lb/src/net/mod.rs new file mode 100644 index 0000000..1556892 --- /dev/null +++ b/crates/lb/src/net/mod.rs @@ -0,0 +1,586 @@ +//! Network library. +//! +//! The `lb:net` library provides an asynchronous network API for woring with TCP, UDP and IPC +//! sockets. +//! +//! ## Exports +//! +//! See [`lb_netlib`] for items exported by this library. +use derive_more::{From, FromStr}; +use luaffi::{cdef, marker::OneOf, metatype}; +use std::{ + cell::{BorrowError, BorrowMutError}, + net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, +}; +use thiserror::Error; +use tokio::net::TcpSocket; + +mod tcp; + +pub use tcp::*; + +/// Errors that can be thrown by this library. +/// +/// Functions which return this error will **throw**. The error message can be caught by using +/// [`pcall(f, ...)`](https://www.lua.org/manual/5.1/manual.html#pdf-pcall). +#[derive(Debug, Error)] +pub enum Error { + /// Attempt to access an object while it is being modified. + #[error("cannot access object while it is being modified")] + Borrow(#[from] BorrowError), + /// Attempt to modify an object while it is in use. + #[error("cannot modify object while it is in use")] + BorrowMut(#[from] BorrowMutError), + /// I/O error. + #[error("{0}")] + Io(#[from] std::io::Error), + /// IP or socket address syntax error. + #[error("{0}")] + InvalidAddr(#[from] AddrParseError), + /// Socket was already converted and can no longer be used. + #[error("socket was already converted")] + SocketConsumed, + /// Socket was closed and can no longer be used. + #[error("socket was closed")] + SocketClosed, + /// Specified socket half was not `"read"` or `"write"`. + #[error("invalid socket half")] + InvalidSocketHalf, +} + +type Result = std::result::Result; + +/// Items exported by the `lb:net` library. +/// +/// This library can be acquired by calling +/// [`require("lb:net")`](https://www.lua.org/manual/5.1/manual.html#pdf-require). +/// +/// ```lua +/// local net = require("lb:net") +/// ``` +#[cdef(module = "lb:net")] +pub struct lb_netlib; + +#[metatype] +impl lb_netlib { + #[new] + extern "Lua-C" fn new() -> Self { + Self + } + + /// An IPv4 address representing localhost: `127.0.0.1` + pub extern "Lua-C" fn localhost() -> lb_ipaddr { + lb_ipaddr(Ipv4Addr::LOCALHOST.into()) + } + + /// An IPv6 address representing localhost: `::1` + pub extern "Lua-C" fn localhost_v6() -> lb_ipaddr { + lb_ipaddr(Ipv6Addr::LOCALHOST.into()) + } + + /// An IPv4 address representing an unspecified address: `0.0.0.0` + pub extern "Lua-C" fn unspecified() -> lb_ipaddr { + lb_ipaddr(Ipv4Addr::UNSPECIFIED.into()) + } + + /// An IPv6 address representing an unspecified address: `::` + pub extern "Lua-C" fn unspecified_v6() -> lb_ipaddr { + lb_ipaddr(Ipv6Addr::UNSPECIFIED.into()) + } + + /// An IPv4 address representing the broadcast address: `255.255.255.255` + pub extern "Lua-C" fn broadcast() -> lb_ipaddr { + lb_ipaddr(Ipv4Addr::BROADCAST.into()) + } + + /// Creates an IP address from the given input. + /// + /// If `addr` is an [`lb_ipaddr`], a copy of that value is returned. If `addr` is an + /// [`lb_socketaddr`], the IP part of the socket address is returned. Otherwise, parses `addr` + /// as an IP address string. Both IPv4 and IPv6 addresses are accepted. + /// + /// An address string may be something like `127.0.0.1`. + /// + /// The type of the parsed address can be checked using [`is_v4`](lb_ipaddr::is_v4) or + /// [`is_v6`](lb_ipaddr::is_v6) functions on the returned [`lb_ipaddr`](lb_ipaddr) value. + /// + /// # Errors + /// + /// This function will throw an error if the input syntax is invalid. + /// + /// # Example + /// + /// ```lua + /// local net = require("lb:net") + /// local addr = net.ipaddr("192.168.1.1") + /// + /// assert(addr:is_v4()) + /// ``` + pub extern "Lua" fn ipaddr( + addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, + ) -> Result { + if __istype(__ct.lb_ipaddr, addr) { + __new(__ct.lb_ipaddr, addr) // copy constructor + } else if __istype(__ct.lb_socketaddr, addr) { + s.ip() + } else { + Self::__parse_ipaddr(addr) + } + } + + extern "Lua-C" fn __parse_ipaddr(addr: &str) -> Result { + Ok(addr.parse()?) + } + + /// Creates a socket address from the given input. + /// + /// A socket address is an IP address with a prescribed port number. + /// + /// If `addr` is an [`lb_socketaddr`], a copy of that value is returned. If `addr` is an + /// [`lb_ipaddr`], a socket address with that IP part is returned. Otherwise, parses `addr` as a + /// socket address string. Both IPv4 and IPv6 addresses are accepted. + /// + /// If `port` is specified, it is always used as the port part of the returned socket address. + /// Otherwise, `0` is used as the default. + /// + /// An address string may be something like `127.0.0.1:3000`. + /// + /// # Errors + /// + /// This function will throw an error if the input syntax is invalid. + /// + /// # Example + /// + /// ```lua + /// local net = require("lb:net") + /// local addr = net.socketaddr("::1", 8080) + /// + /// assert(addr:ip():is_v6() and addr:ip():is_loopback()) + /// assert(addr:port() == 8080) + /// ``` + pub extern "Lua" fn socketaddr( + addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, + port: Option, + ) -> Result { + if port != () { + Self::__new_skaddr(Self::ipaddr(addr), port) + } else { + if __istype(__ct.lb_socketaddr, addr) { + __new(__ct.lb_socketaddr, addr) // copy constructor + } else if __istype(__ct.lb_ipaddr, addr) { + Self::__new_skaddr(addr, 0) // default port 0 + } else { + Self::__parse_skaddr(addr) + } + } + } + + extern "Lua-C" fn __new_skaddr(ip: &lb_ipaddr, port: u16) -> lb_socketaddr { + SocketAddr::new(ip.0, port).into() + } + + extern "Lua-C" fn __parse_skaddr(addr: &str) -> Result { + Ok(if let Ok(addr) = addr.parse() { + SocketAddr::new(addr, 0).into() // default port 0 + } else { + addr.parse::()?.into() + }) + } + + /// Creates a new TCP socket configured for IPv4. + /// + /// This calls `socket(2)` with `AF_INET` and `SOCK_STREAM`. + /// + /// # Errors + /// + /// This function may throw an error if the socket could not be created. + pub extern "Lua-C" fn tcp() -> Result { + Ok(lb_tcpsocket::new(TcpSocket::new_v4()?)) + } + + /// Creates a new TCP socket configured for IPv6. + /// + /// This calls `socket(2)` with `AF_INET6` and `SOCK_STREAM`. + /// + /// # Errors + /// + /// This function may throw an error if the socket could not be created. + pub extern "Lua-C" fn tcp_v6() -> Result { + Ok(lb_tcpsocket::new(TcpSocket::new_v6()?)) + } + + /// Creates a new TCP socket bound to the given address and port. + /// + /// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a + /// new TCP socket configured to use either IPv4 or IPv6 depending on the type of the given + /// address, and binds it to that address. + /// + /// # Errors + /// + /// This function may throw an error if the socket could not be created or bound to the + /// specified address. + /// + /// # Example + /// + /// ```lua + /// local net = require("lb:net") + /// local socket = net.bind_tcp("127.0.0.1", 8080) + /// + /// assert(socket:local_addr() == net.socketaddr("127.0.0.1:8080")) + /// socket:set_nodelay(true) + /// ``` + pub extern "Lua" fn bind_tcp( + addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, + port: Option, + ) -> Result { + let addr = Self::socketaddr(addr, port); + let socket; + if addr.ip().is_v6() { + socket = Self::tcp_v6(); + } else { + socket = Self::tcp(); + } + socket.bind(addr); + socket + } + + /// Creates a new TCP socket listening on the given address and port. + /// + /// This is a convenience function that combines [`bind_tcp`](Self::bind_tcp) and + /// [`listen`](lb_tcpsocket::listen). It accepts the same arguments as + /// [`socketaddr`](Self::socketaddr). + /// + /// # Errors + /// + /// This function may throw an error if the socket could not be created, bound to the specified + /// address, or could not transition to the listening state. + /// + /// # Example + /// + /// ```lua + /// local net = require("lb:net") + /// local listener = net.listen_tcp("127.0.0.1", 1234) + /// + /// assert(listener:local_addr() == net.socketaddr("127.0.0.1:1234")) + /// + /// for stream in listener do + /// print("client connected: ", stream:remote_addr()) + /// end + pub extern "Lua" fn listen_tcp( + addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, + port: Option, + ) -> Result { + Self::bind_tcp(addr, port).listen(1024) + } + + /// Establishes a new TCP connection to the server at the given address and port. + /// + /// This function accepts the same arguments as [`socketaddr`](Self::socketaddr). It creates a + /// new TCP socket and connects it to the specified address. + /// + /// # Errors + /// + /// This function may throw an error if the socket could not be created or connected to the + /// specified address. + /// + /// # Example + /// + /// ```lua + /// local net = require("lb:net") + /// local stream = net.connect_tcp("127.0.0.1", 1234) + /// + /// assert(stream:remote_addr() == net.socketaddr("127.0.0.1:1234")) + /// stream:write("Hello, server!\n") + /// ``` + pub async extern "Lua" fn connect_tcp( + addr: OneOf<(&str, &lb_ipaddr, &lb_socketaddr)>, + port: Option, + ) -> Result { + let addr = Self::socketaddr(addr, port); + let socket; + if addr.ip().is_v6() { + socket = Self::tcp_v6(); + } else { + socket = Self::tcp(); + } + socket.connect(addr) + } +} + +/// IP address, either IPv4 or IPv6. +/// +/// # Example +/// +/// This example creates an [`lb_ipaddr`] by parsing an IP address string. +/// +/// ```lua +/// local net = require("lb:net") +/// local addr = net.ipaddr("127.0.0.1") -- ipv4 loopback address +/// +/// assert(addr:is_v4() and addr:is_loopback()) +/// assert(tostring(addr) == "127.0.0.1") +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)] +#[cdef] +pub struct lb_ipaddr(#[opaque] IpAddr); + +#[metatype] +impl lb_ipaddr { + /// Returns `true` if this is an IPv4 address. + pub extern "Lua-C" fn is_v4(&self) -> bool { + self.0.is_ipv4() + } + + /// Returns `true` if this is an IPv6 address. + pub extern "Lua-C" fn is_v6(&self) -> bool { + self.0.is_ipv6() + } + + /// Returns the string `"v4"` if this is an IPv4 address, or `"v6"` if this is an IPv6 address. + pub extern "Lua" fn family(&self) -> String { + if self.is_v6() { "v6" } else { "v4" } + } + + /// Returns `true` if this is a loopback address. + /// + /// For IPv4, this is any address in the `127.0.0.0/8` range. + /// + /// For IPv6, this is the address `::1`. + pub extern "Lua-C" fn is_loopback(&self) -> bool { + self.0.is_loopback() + } + + /// Returns `true` if this address is unspecified. + /// + /// For IPv4, this is the address `0.0.0.0`. + /// + /// For IPv6, this is the address `::`. + pub extern "Lua-C" fn is_unspecified(&self) -> bool { + self.0.is_unspecified() + } + + /// Returns `true` if this address is a multicast address. + /// + /// For IPv4, this is any address in the `224.0.0.0/4` range. + /// + /// For IPv6, this is any address in the `ff00::/8` range. + pub extern "Lua-C" fn is_multicast(&self) -> bool { + self.0.is_multicast() + } + + /// Returns `true` if this is an IPv4 private address. + /// + /// For IPv4, this is any address in one of these ranges: + /// + /// - `10.0.0.0/8` + /// - `172.16.0.0/12` + /// - `192.168.0.0/16` + /// + /// For IPv6, this always returns `false`. + pub extern "Lua-C" fn is_v4_private(&self) -> bool { + match self.0 { + IpAddr::V4(v4) => v4.is_private(), + IpAddr::V6(_) => false, + } + } + + /// Returns `true` if this is an IPv4 link-local address. + /// + /// For IPv4, this is any address in the `169.254.0.0/16` range. + /// + /// For IPv6, this always returns `false`. + pub extern "Lua-C" fn is_v4_link_local(&self) -> bool { + match self.0 { + IpAddr::V4(v4) => v4.is_link_local(), + IpAddr::V6(_) => false, + } + } + + /// Returns `true` if this is an IPv4 broadcast address. + /// + /// For IPv4, this is the address `255.255.255.255`. + /// + /// For IPv6, this always returns `false`. + pub extern "Lua-C" fn is_v4_broadcast(&self) -> bool { + match self.0 { + IpAddr::V4(v4) => v4.is_broadcast(), + IpAddr::V6(_) => false, + } + } + + /// Returns `true` if this is an IPv4 documentation address. + /// + /// For IPv4, this is any address in one of these ranges: + /// + /// - `192.0.2.0/24` (TEST-NET-1) + /// - `198.51.100.0/24` (TEST-NET-2) + /// - `203.0.113.0/24` (TEST-NET-3) + /// + /// For IPv6, this always returns `false`. + pub extern "Lua-C" fn is_v4_documentation(&self) -> bool { + match self.0 { + IpAddr::V4(v4) => v4.is_documentation(), + IpAddr::V6(_) => false, + } + } + + /// Returns `true` if this is an IPv6 unique local address. + /// + /// For IPv4, this always returns `false`. + /// + /// For IPv6, this is any address in the `fc00::/7` range. + pub extern "Lua-C" fn is_v6_unique_local(&self) -> bool { + match self.0 { + IpAddr::V4(_) => false, + IpAddr::V6(v6) => v6.is_unique_local(), + } + } + + /// Returns `true` if this is an IPv6 unicast address with link-local scope. + /// + /// For IPv4, this always returns `false`. + /// + /// For IPv6, this is any address in the `fe80::/10` range. + pub extern "Lua-C" fn is_v6_unicast_link_local(&self) -> bool { + match self.0 { + IpAddr::V4(_) => false, + IpAddr::V6(v6) => v6.is_unicast_link_local(), + } + } + + /// Converts this address to IPv4. + /// + /// For IPv4, this returns the address unchanged. + /// + /// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible + /// IPv6 address. Otherwise, this returns `nil`. + pub extern "Lua-C" fn to_v4(&self) -> Option { + match self.0 { + IpAddr::V4(_) => Some(*self), + IpAddr::V6(v6) => v6.to_ipv4().map(|v| Self(v.into())), + } + } + + /// Converts this address to IPv6. + /// + /// For IPv4, this returns the IPv4-mapped IPv6 address as defined by + /// [`Ipv4Addr::to_ipv6_mapped`]. + /// + /// For IPv6, this returns the address unchanged. + pub extern "Lua-C" fn to_v6(&self) -> Self { + match self.0 { + IpAddr::V4(v4) => Self(v4.to_ipv6_mapped().into()), + IpAddr::V6(_) => *self, + } + } + + /// Returns the canonical form of this address. + /// + /// For IPv4, this returns the address unchanged. + /// + /// For IPv6, this returns the original IPv4 address if it is an IPv4-mapped or IPv4-compatible + /// IPv6 address. Otherwise, this returns the address unchanged. + pub extern "Lua-C" fn to_canonical(&self) -> Self { + match self.0 { + IpAddr::V4(_) => *self, + IpAddr::V6(v6) => v6.to_ipv4().map_or(*self, |v| Self(v.into())), + } + } + + /// Returns `true` if the given addresses are equal. + /// + /// Two addresses are considered equal if they are of the same family (IPv4 or IPv6) and + /// represent the same address in octets. + #[eq] + pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool { + left.0 == right.0 + } + + /// Returns `true` if the left address is less than the right address. + /// + /// IPv4 addresses are always less than IPv6 addresses. + #[lt] + pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool { + left.0 < right.0 + } + + /// Returns `true` if the left address is less than or equal to the right address. + /// + /// IPv4 addresses are always less than IPv6 addresses. + #[le] + pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool { + left.0 <= right.0 + } + + /// Returns the string representation of this address. + #[tostring] + pub extern "Lua-C" fn tostring(&self) -> String { + self.0.to_string() + } +} + +/// Socket address, which is an IP address with a port number. +/// +/// This represents an IP address with a prescribed port, such as `127.0.0.1:8080` or `[::1]:443`. +/// It is used to specify endpoints for network connections and listeners, and can be constructed by +/// [`socketaddr`](lb_netlib::socketaddr). +/// +/// # Example +/// +/// ```lua +/// local net = require("lb:net") +/// local addr = net.socketaddr("127.0.0.1:8080") +/// +/// assert(addr:ip() == net.ipaddr("127.0.0.1") and addr:port() == 8080) +/// assert(tostring(addr) == "127.0.0.1:8080") +/// ``` +#[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)] +#[cdef] +pub struct lb_socketaddr(#[opaque] SocketAddr); + +#[metatype] +impl lb_socketaddr { + /// The IP part of this address. + pub extern "Lua-C" fn ip(&self) -> lb_ipaddr { + self.0.ip().into() + } + + /// The port part of this address. + pub extern "Lua-C" fn port(&self) -> u16 { + self.0.port() + } + + /// Returns a new socket address with the given IP address and the same port. + pub extern "Lua-C" fn with_ip(&self, ip: &lb_ipaddr) -> Self { + SocketAddr::new(ip.0, self.port()).into() + } + + /// Returns a new socket address with the given port and the same IP address. + pub extern "Lua-C" fn with_port(&self, port: u16) -> Self { + SocketAddr::new(self.ip().0, port).into() + } + + /// Returns `true` if the given addresses are equal. + #[eq] + pub extern "Lua-C" fn equals(left: &Self, right: &Self) -> bool { + left.0 == right.0 + } + + /// Returns `true` if the left address is less than the right address. + #[lt] + pub extern "Lua-C" fn less_than(left: &Self, right: &Self) -> bool { + left.0 < right.0 + } + + /// Returns `true` if the left address is less than or equal to the right address. + #[le] + pub extern "Lua-C" fn less_than_or_equals(left: &Self, right: &Self) -> bool { + left.0 <= right.0 + } + + /// Returns the string representation of this address. + #[tostring] + pub extern "Lua-C" fn tostring(&self) -> String { + self.0.to_string() + } +} diff --git a/crates/lb/src/net/tcp.rs b/crates/lb/src/net/tcp.rs new file mode 100644 index 0000000..7207d15 --- /dev/null +++ b/crates/lb/src/net/tcp.rs @@ -0,0 +1,619 @@ +use super::*; +use luaffi::{cdef, marker::fun, metatype}; +use luajit::LUA_NOREF; +use std::io::ErrorKind; +use std::{ + cell::{Ref, RefCell, RefMut}, + ffi::c_int, + time::Duration, +}; +use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; +use tokio::{ + io::{AsyncReadExt, AsyncWriteExt, Interest}, + net::{TcpListener, TcpSocket, TcpStream}, +}; + +/// TCP socket which has not yet been converted to an [`lb_tcpstream`] or [`lb_tcplistener`]. +/// +/// This type represents a TCP socket in its initial state, before it is connected or set to listen. +/// It can be configured (e.g., socket options, bind address) before being converted to an +/// [`lb_tcpstream`] (via [`connect`](lb_tcpsocket::connect)) or [`lb_tcplistener`] (via +/// [`listen`](lb_tcpsocket::listen)), after which it can no longer be used. +/// +/// Methods on this type may fail if the operating system does not support the requested operation. +#[derive(Debug)] +#[cdef] +pub struct lb_tcpsocket(#[opaque] RefCell>); + +#[metatype] +impl lb_tcpsocket { + pub(super) fn new(socket: TcpSocket) -> Self { + Self(RefCell::new(Some(socket))) + } + + fn socket<'s>(&'s self) -> Result> { + let socket = self.0.borrow(); + match *socket { + Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())), + None => Err(Error::SocketConsumed), + } + } + + /// Gets the value of the `SO_KEEPALIVE` option on this socket. + pub extern "Lua-C" fn keepalive(&self) -> Result { + Ok(self.socket()?.keepalive()?) + } + + /// Sets value for the `SO_KEEPALIVE` option on this socket. + /// + /// This enables or disables periodic keepalive messages on the connection. + pub extern "Lua-C" fn set_keepalive(&self, enabled: bool) -> Result<()> { + Ok(self.socket()?.set_keepalive(enabled)?) + } + + /// Gets the value of the `SO_REUSEADDR` option on this socket. + pub extern "Lua-C" fn reuseaddr(&self) -> Result { + Ok(self.socket()?.reuseaddr()?) + } + + /// Sets value for the `SO_REUSEADDR` option on this socket. + /// + /// This allows the socket to bind to an address that is already in use. + pub extern "Lua-C" fn set_reuseaddr(&self, enabled: bool) -> Result<()> { + Ok(self.socket()?.set_reuseaddr(enabled)?) + } + + /// Gets the value of the `SO_REUSEPORT` option on this socket. + pub extern "Lua-C" fn reuseport(&self) -> Result { + Ok(self.socket()?.reuseport()?) + } + + /// Sets value for the `SO_REUSEPORT` option on this socket. + /// + /// This allows multiple sockets to bind to the same port. + pub extern "Lua-C" fn set_reuseport(&self, enabled: bool) -> Result<()> { + Ok(self.socket()?.set_reuseport(enabled)?) + } + + /// Gets the value of the `SO_SNDBUF` option on this socket. + pub extern "Lua-C" fn sendbuf(&self) -> Result { + Ok(self.socket()?.send_buffer_size()?) + } + + /// Sets value for the `SO_SNDBUF` option on this socket. + /// + /// This sets the size of the send buffer in bytes. + pub extern "Lua-C" fn set_sendbuf(&self, size: u32) -> Result<()> { + Ok(self.socket()?.set_send_buffer_size(size)?) + } + + /// Gets the value of the `SO_RCVBUF` option on this socket. + pub extern "Lua-C" fn recvbuf(&self) -> Result { + Ok(self.socket()?.recv_buffer_size()?) + } + + /// Sets value for the `SO_RCVBUF` option on this socket. + /// + /// This sets the size of the receive buffer in bytes. + pub extern "Lua-C" fn set_recvbuf(&self, size: u32) -> Result<()> { + Ok(self.socket()?.set_recv_buffer_size(size)?) + } + + /// Gets the value of the `SO_LINGER` option on this socket, in seconds. + pub extern "Lua-C" fn linger(&self) -> Result { + Ok(self + .socket()? + .linger()? + .map(|n| n.as_secs_f64()) + .unwrap_or(0.)) + } + + /// Sets the value of the `SO_LINGER` option on this socket. + /// + /// This controls how long the socket will remain open after close if unsent data is present. + pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> { + let secs = secs.max(0.); + Ok(self + .socket()? + .set_linger((secs != 0.).then_some(Duration::from_secs_f64(secs)))?) + } + + /// Gets the value of the `TCP_NODELAY` option on this socket. + pub extern "Lua-C" fn nodelay(&self) -> Result { + Ok(self.socket()?.nodelay()?) + } + + /// Sets the value of the `TCP_NODELAY` option on this socket. + /// + /// This enables or disables Nagle's algorithm, which delays sending small packets. + pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> { + Ok(self.socket()?.set_nodelay(enabled)?) + } + + /// Gets the local address that this socket is bound to. + pub extern "Lua-C" fn local_addr(&self) -> Result { + Ok(self.socket()?.local_addr()?.into()) + } + + /// Binds this socket to the given local address. + pub extern "Lua-C" fn bind(&self, addr: &lb_socketaddr) -> Result<()> { + Ok(self.socket()?.bind(addr.0)?) + } + + /// Transitions this socket to the listening state. + /// + /// This consumes the socket and returns a new [`lb_tcplistener`] that can accept incoming + /// connections. This socket object can no longer be used after this call. + pub extern "Lua-C" fn listen(&self, backlog: u32) -> Result { + let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?; + Ok(lb_tcplistener::new(socket.listen(backlog)?)) + } + + /// Connects this socket to the given remote socket address, transitioning it to an established + /// state. + /// + /// This consumes the socket and returns a new [`lb_tcpstream`] that can be used to send and + /// receive data. This socket object can no longer be used after this call. + /// + /// # Errors + /// + /// This function may throw an error if connection could not be established to the given remote + /// address. + pub async extern "Lua-C" fn connect(&self, addr: &lb_socketaddr) -> Result { + let socket = self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?; + Ok(lb_tcpstream::new(socket.connect(addr.0).await?)) + } +} + +/// TCP connection between a local and a remote socket. +/// +/// This represents an established TCP connection. It is created by connecting an [`lb_tcpsocket`] +/// to a remote socket (via [`connect`](lb_tcpsocket::connect)) or accepting a connection from an +/// [`lb_tcplistener`] (via [`accept`](lb_tcplistener::accept)). It provides methods for reading +/// from and writing to the stream asynchronously. +/// +/// The stream supports reading and writing data in both directions concurrently. Typically you +/// would spawn one reader task and one writer task to handle incoming and outgoing data +/// respectively. Connection is closed when this object goes out of scope and gets garbage +/// collected, or when [`close`](Self::close) is explicitly called. +/// +/// Methods on this type may fail if the operating system does not support the requested operation. +/// +/// # Example +/// +/// This examples spawns a reader task and a writer task to operate on the stream concurrently. +/// +/// ```lua +/// local task = require("lb:task") +/// local net = require("lb:net") +/// local socket = net.connect_tcp("127.0.0.1:1234") +/// +/// print("local address: ", socket:local_addr()) +/// print("remote address: ", socket:peer_addr()) +/// +/// local reader = spawn(function() +/// for chunk in socket, 1024 do +/// print("received: ", chunk) +/// done +/// +/// print("done reading") +/// end) +/// +/// local writer = spawn(function() +/// for i = 1, 10 do +/// local msg = ("message %d\n"):format(i) +/// socket:write(msg) +/// print("sent: ", msg) +/// done +/// +/// print("done writing") +/// end) +/// +/// task.join(reader, writer) +/// ``` +/// +/// The above example uses the socket as an iterator in a generic `for` loop to read data in chunks +/// of up to 1024 bytes. It is equivalent to the following: +/// +/// ```lua +/// while true do +/// local chunk = socket:read_partial(1024) +/// if chunk == nil then break end +/// print("received: ", chunk) +/// end +/// ``` +#[derive(Debug)] +#[cdef] +pub struct lb_tcpstream { + #[opaque] + read: RefCell>, + #[opaque] + write: RefCell>, +} + +#[metatype] +impl lb_tcpstream { + pub(super) fn new(stream: TcpStream) -> Self { + let (read, write) = stream.into_split(); + Self { + read: RefCell::new(Some(read)), + write: RefCell::new(Some(write)), + } + } + + fn read_half<'s>(&'s self) -> Result> { + let read = self.read.borrow_mut(); + match *read { + Some(_) => Ok(RefMut::map(read, |s| s.as_mut().unwrap())), + None => Err(Error::SocketClosed), + } + } + + fn write_half<'s>(&'s self) -> Result> { + let write = self.write.borrow_mut(); + match *write { + Some(_) => Ok(RefMut::map(write, |s| s.as_mut().unwrap())), + None => Err(Error::SocketClosed), + } + } + + /// The local socket address that this stream is bound to. + pub extern "Lua-C" fn local_addr(&self) -> Result { + Ok(self.read_half()?.local_addr()?.into()) + } + + /// The remote socket address that this stream is connected to. + pub extern "Lua-C" fn peer_addr(&self) -> Result { + Ok(self.read_half()?.peer_addr()?.into()) + } + + /// Waits for this stream to be ready in the given half. + /// + /// The argument `half` can be `"read"` for the readable half, `"write"` for the writable half, + /// or `nil` for both. + pub async extern "Lua-C" fn ready(&self, half: Option<&str>) -> Result<()> { + self.read_half()? + .ready(match half { + Some("read") => Interest::READABLE, + Some("write") => Interest::WRITABLE, + None => Interest::READABLE | Interest::WRITABLE, + _ => Err(Error::InvalidSocketHalf)?, + }) + .await?; + + Ok(()) + } + + /// Closes this stream in the given half. + /// + /// The argument `half` can be `"read"` for the readable half, `"write"` for the writable half, + /// or `nil` for both. + /// + /// Once the half is closed, it can no longer be used for reading or writing for that half. Once + /// both halves are closed, the stream is fully shut down. + pub extern "Lua-C" fn close(&self, half: Option<&str>) -> Result<()> { + Ok(match half { + Some("read") => drop(self.read.try_borrow_mut()?.take()), + Some("write") => drop(self.write.try_borrow_mut()?.take()), + None => drop(( + self.read.try_borrow_mut()?.take(), + self.write.try_borrow_mut()?.take(), + )), + _ => Err(Error::InvalidSocketHalf)?, + }) + } + + fn is_disc(err: ErrorKind) -> bool { + matches!( + err, + ErrorKind::ConnectionReset // graceful shutdown + | ErrorKind::BrokenPipe // abrupt shutdown + | ErrorKind::UnexpectedEof // could not read requested amount of data + | ErrorKind::WriteZero // could not write requested amount of data + ) + } + + /// Reads exactly `len` bytes from this stream. + /// + /// If the connection was closed, this returns `nil`. + pub async extern "Lua-C" fn read(&self, len: u32) -> Result>> { + let mut buf = vec![0; len as usize]; + Ok(match self.read_half()?.read_exact(&mut buf).await { + Ok(_) => Some(buf), + Err(err) if Self::is_disc(err.kind()) => None, + Err(err) => return Err(err.into()), + }) + } + + /// Reads up to `len` bytes from this stream. + /// + /// The returned bytes may be less than `len` in length if the stream had less data available in + /// queue. If there was no data available or the connection was closed, this returns `nil`. + pub async extern "Lua-C" fn read_partial(&self, len: u32) -> Result>> { + let mut buf = vec![0; len as usize]; + Ok(match self.read_half()?.read(&mut buf).await { + Ok(0) => None, + Ok(n) => Some({ + buf.truncate(n); + buf + }), + Err(err) if Self::is_disc(err.kind()) => None, + Err(err) => return Err(err.into()), + }) + } + + /// Attempts to read up to `len` bytes from this stream without waiting. + /// + /// The returned bytes may be less than `len` in length if the stream had less data available in + /// queue. If there was no data available or the connection was closed, this returns `nil`. + pub extern "Lua-C" fn try_read(&self, len: u32) -> Result>> { + let mut buf = vec![0; len as usize]; + Ok(match self.read_half()?.try_read(&mut buf) { + Ok(0) => None, + Ok(n) => Some({ + buf.truncate(n); + buf + }), + Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None, + Err(err) => return Err(err.into()), + }) + } + + /// Writes exactly the given bytes to this stream. + /// + /// If the connection was closed, this returns `false`. + pub async extern "Lua-C" fn write(&self, buf: &[u8]) -> Result { + Ok(match self.write_half()?.write_all(buf).await { + Ok(()) => true, + Err(err) if Self::is_disc(err.kind()) => false, + Err(err) => return Err(err.into()), + }) + } + + /// Writes the given bytes to this stream, and returns the number of bytes successfully written. + /// + /// The returned number may be less than the length of `buf` if there was not enough space in + /// queue. If the connection was closed, this returns `nil`. + pub async extern "Lua-C" fn write_partial(&self, buf: &[u8]) -> Result> { + Ok(match self.write_half()?.write(buf).await { + Ok(n) => Some(n as u32), + Err(err) if Self::is_disc(err.kind()) => None, + Err(err) => return Err(err.into()), + }) + } + + /// Attempts to write the given bytes to this stream without waiting, and returns the number of + /// bytes successfully written. + /// + /// The returned number may be less than the length of `buf` if there was not enough space in + /// queue. If the connection was closed, this returns `nil`. + pub extern "Lua-C" fn try_write(&self, buf: &[u8]) -> Result> { + Ok(match self.write_half()?.try_write(buf) { + Ok(n) => Some(n as u32), + Err(err) if Self::is_disc(err.kind()) || err.kind() == ErrorKind::WouldBlock => None, + Err(err) => return Err(err.into()), + }) + } + + /// Peeks up to `len` bytes at incoming data without consuming it. + /// + /// Successive calls will return the same data until it is consumed by the [`read*`](Self::read) + /// family of functions. + pub async extern "Lua-C" fn peek(&self, len: u32) -> Result>> { + let mut buf = vec![0; len as usize]; + Ok(match self.read_half()?.peek(&mut buf).await { + Ok(0) => None, + Ok(n) => Some({ + buf.truncate(n); + buf + }), + Err(err) if Self::is_disc(err.kind()) => None, + Err(err) => return Err(err.into()), + }) + } + + /// Alias for [`read_partial`](Self::read_partial). + #[call] + pub async extern "Lua" fn call(&self, len: u32) -> Result>> { + self.read_partial(len) + } +} + +/// TCP socket server, listening for connections. +/// +/// This type represents a TCP server socket that can accept incoming connections. It is created by +/// transitioning an [`lb_tcpsocket`] to the listening state via [`listen`](lb_tcpsocket::listen). +/// +/// Methods on this type may fail if the operating system does not support the requested operation. +/// +/// # Example +/// +/// The listener can be used as an iterator in a generic `for` loop to accept incoming connections: +/// +/// ```lua +/// local net = require("lb:net") +/// local listener = net.listen_tcp("127.0.0.1") +/// +/// print("listening on: ", listener:local_addr()) +/// +/// for stream in listener do +/// print("accepted connection from: ", stream:peer_addr()) +/// print("local address: ", stream:local_addr()) +/// +/// spawn(function() +/// stream:write("hello from server\n") +/// stream:close() +/// end) +/// end +/// ``` +#[derive(Debug)] +#[cdef] +pub struct lb_tcplistener { + #[opaque] + listener: TcpListener, + __on_accept_ref: c_int, +} + +#[metatype] +impl lb_tcplistener { + pub(super) fn new(listener: TcpListener) -> Self { + Self { + listener, + __on_accept_ref: LUA_NOREF, + } + } + + /// The local socket address that this listener is bound to. + pub extern "Lua-C" fn local_addr(&self) -> Result { + Ok(self.listener.local_addr()?.into()) + } + + /// Registers a callback to be invoked with each new incoming connection before it is converted + /// to an [`lb_tcpstream`]. + /// + /// The callback receives a temporary [`lb_tcplistener_stream`] object, which can be used to log + /// incoming connections or configure socket options (such as + /// [`set_nodelay`](lb_tcplistener_stream), [`set_linger`](lb_tcplistener_stream), etc.) before + /// it is converted to an [`lb_tcpstream`]. The callback is called synchronously during + /// [`accept`](Self::accept) and should complete as quickly as possible. The provided + /// configurable object is only valid within the callback and is converted to an + /// [`lb_tcpstream`] as soon as it returns. + /// + /// If a callback already exists, it is replaced with the new one. + /// + /// # Example + /// + /// ```lua + /// local net = require("lb:net") + /// local listener = net.listen_tcp("127.0.0.1") + /// + /// listener:on_accept(function(stream) + /// print("accepted connection from: ", stream:peer_addr()) + /// print("local address: ", stream:local_addr()) + /// + /// stream:set_nodelay(true) + /// end) + /// ``` + pub extern "Lua" fn on_accept(&self, cb: fun<(&lb_tcplistener_stream,), ()>) { + assert( + rawequal(cb, ()) || r#type(cb) == "function", + concat!("function expected in argument 'cb', got ", r#type(cb)), + ); + __unref(self.__on_accept_ref); + self.__on_accept_ref = __ref(cb); + } + + /// Accepts a new incoming TCP connection. + /// + /// If an [`on_accept`](Self::on_accept) callback is registered, it is invoked with a temporary + /// [`lb_tcplistener_stream`] object representing the new connection. This allows configuration + /// of socket options for this specific connection, before the stream is converted to an + /// [`lb_tcpstream`] and returned for the connection to be read from or written to. + pub async extern "Lua" fn accept(&self) -> Result { + let stream = self.__accept(); + let on_accept = __registry[self.__on_accept_ref]; + if !rawequal(on_accept, ()) { + on_accept(stream); + } + stream.__convert() + } + + async extern "Lua-C" fn __accept(&self) -> Result { + let (stream, _) = self.listener.accept().await?; + Ok(lb_tcplistener_stream::new(stream)) + } + + /// Alias for [`accept`](Self::accept). + #[call] + pub async extern "Lua" fn call(&self) -> Result { + self.accept() + } + + #[gc] + extern "Lua" fn gc(&self) { + __unref(self.__on_accept_ref); + } +} + +/// TCP connection that has just been accepted by [`lb_tcplistener`]. +/// +/// This type is passed to the [`on_accept`](lb_tcplistener::on_accept) callback on +/// [`lb_tcplistener`], allowing socket options to be set before the stream is converted to an +/// [`lb_tcpstream`]. After conversion, this object can no longer be used. +/// +/// Methods on this type may fail if the operating system does not support the requested operation. +#[derive(Debug)] +#[cdef] +pub struct lb_tcplistener_stream(#[opaque] RefCell>); + +#[metatype] +impl lb_tcplistener_stream { + fn new(stream: TcpStream) -> Self { + Self(RefCell::new(Some(stream))) + } + + fn stream<'s>(&'s self) -> Result> { + let socket = self.0.borrow(); + match *socket { + Some(_) => Ok(Ref::map(socket, |s| s.as_ref().unwrap())), + None => Err(Error::SocketConsumed), + } + } + + /// The local socket address that the listener is bound to. + pub extern "Lua-C" fn local_addr(&self) -> Result { + Ok(self.stream()?.local_addr()?.into()) + } + + /// The remote socket address that this stream is connected to. + pub extern "Lua-C" fn peer_addr(&self) -> Result { + Ok(self.stream()?.peer_addr()?.into()) + } + + /// Gets the value of the `TCP_NODELAY` option on this stream. + pub extern "Lua-C" fn nodelay(&self) -> Result { + Ok(self.stream()?.nodelay()?) + } + + /// Sets the value of the `TCP_NODELAY` option on this stream. + /// + /// This enables or disables Nagle's algorithm, which delays sending small packets. + pub extern "Lua-C" fn set_nodelay(&self, enabled: bool) -> Result<()> { + Ok(self.stream()?.set_nodelay(enabled)?) + } + + /// Gets the value of the `SO_LINGER` option on this stream, in seconds. + pub extern "Lua-C" fn linger(&self) -> Result { + Ok(self + .stream()? + .linger()? + .map(|n| n.as_secs_f64()) + .unwrap_or(0.)) + } + + /// Sets the value of the `SO_LINGER` option on this stream. + /// + /// This controls how long the stream will remain open after close if unsent data is present. + pub extern "Lua-C" fn set_linger(&self, secs: f64) -> Result<()> { + let secs = secs.max(0.); + Ok(self + .stream()? + .set_linger((secs != 0.).then_some(std::time::Duration::from_secs_f64(secs)))?) + } + + /// Gets the value of the `IP_TTL` option for this stream. + pub extern "Lua-C" fn ttl(&self) -> Result { + Ok(self.stream()?.ttl()?) + } + + /// Sets the value for the `IP_TTL` option on this stream. + pub extern "Lua-C" fn set_ttl(&self, ttl: u32) -> Result<()> { + Ok(self.stream()?.set_ttl(ttl)?) + } + + extern "Lua-C" fn __convert(&self) -> Result { + Ok(lb_tcpstream::new( + self.0.borrow_mut().take().ok_or(Error::SocketConsumed)?, + )) + } +}