//! # Networking library //! //! The `lb:net` library provides an asynchronous network API for creating TCP or UDP servers and //! clients. //! //! ## 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::{ 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** in Lua. 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) in Lua. /// /// ```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 a combination of an IP address and a 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_libnet::socketaddr). #[derive(Debug, Clone, Copy, PartialEq, Eq, From, FromStr)] #[cdef] pub struct lb_socketaddr(#[opaque] SocketAddr); #[metatype] impl lb_socketaddr { /// Returns the IP part of this address. pub extern "Lua-C" fn ip(&self) -> lb_ipaddr { self.0.ip().into() } /// Returns 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()?) } /// Gets the remote socket address of this stream. pub extern "Lua-C" fn peer_addr(&self) -> Result { Ok(self.read_half()?.peer_addr()?.into()) } /// Gets the local socket address of this stream. pub extern "Lua-C" fn local_addr(&self) -> Result { Ok(self.read_half()?.local_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( std::io::ErrorKind::InvalidInput, "invalid ready interest", ))?, }; self.read_half()?.ready(ty).await?; Ok(()) } /// 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 err.kind() == std::io::ErrorKind::UnexpectedEof => 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]; let n = self.read_half()?.read(&mut buf).await?; Ok(if n == 0 { None } else { buf.truncate(n); Some(buf) }) } /// 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) => { buf.truncate(n); Some(buf) } Err(err) if err.kind() == std::io::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(self.write_half()?.write_all(buf).await?) } /// 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(self.write_half()?.write(buf).await? as u32) } /// 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]; let n = self.read_half()?.peek(&mut buf).await?; Ok(if n == 0 { None } else { buf.truncate(n); Some(buf) }) } /// 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, } } /// Returns 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), } } /// Returns 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()) } /// Returns the remote socket address of this stream. 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)?, )) } }