From 8c47987a45e59ddeb777fa7672d66a5080c5f7c5 Mon Sep 17 00:00:00 2001 From: luaneko Date: Tue, 24 Jun 2025 22:49:02 +1000 Subject: [PATCH] Implement basic net module --- crates/lb/src/lib.rs | 4 +- crates/lb/src/net.rs | 345 +++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 1 + src/main.rs | 112 +++++++------- 4 files changed, 408 insertions(+), 54 deletions(-) create mode 100644 crates/lb/src/net.rs create mode 100644 src/lib.rs diff --git a/crates/lb/src/lib.rs b/crates/lb/src/lib.rs index 2617443..b7e63d1 100644 --- a/crates/lb/src/lib.rs +++ b/crates/lb/src/lib.rs @@ -1,2 +1,4 @@ -pub mod rt; +pub mod channel; +pub mod net; +pub mod runtime; pub mod task; diff --git a/crates/lb/src/net.rs b/crates/lb/src/net.rs new file mode 100644 index 0000000..aa542e4 --- /dev/null +++ b/crates/lb/src/net.rs @@ -0,0 +1,345 @@ +//! The `lb:net` module provides an asynchronous network API for creating TCP or UDP servers and +//! clients. +//! +//! See [`lb_libnet`] for items exported by this module. +use derive_more::{From, FromStr}; +use luaffi::{cdef, metatype}; +use std::{ + io, + net::{AddrParseError, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}, +}; +use tokio::net::{TcpListener, TcpSocket, TcpStream}; + +/// Items exported by the `lb:net` module. +/// +/// This module can be obtained by calling `require` in Lua. +/// +/// ```lua +/// local net = require("lb:net"); +/// ``` +#[cdef] +pub struct lb_libnet; + +#[metatype] +impl lb_libnet { + #[new] + extern "Lua-C" fn new() -> Self { + Self + } + + /// See [`Ipv4Addr::LOCALHOST`]. + pub extern "Lua-C" fn localhost_v4(&self) -> lb_ipaddr { + lb_ipaddr(Ipv4Addr::LOCALHOST.into()) + } + + /// See [`Ipv6Addr::LOCALHOST`]. + pub extern "Lua-C" fn localhost_v6(&self) -> lb_ipaddr { + lb_ipaddr(Ipv6Addr::LOCALHOST.into()) + } + + /// See [`Ipv4Addr::UNSPECIFIED`]. + pub extern "Lua-C" fn unspecified_v4(&self) -> lb_ipaddr { + lb_ipaddr(Ipv4Addr::UNSPECIFIED.into()) + } + + /// See [`Ipv6Addr::UNSPECIFIED`]. + pub extern "Lua-C" fn unspecified_v6(&self) -> lb_ipaddr { + lb_ipaddr(Ipv6Addr::UNSPECIFIED.into()) + } + + /// See [`Ipv4Addr::BROADCAST`]. + pub extern "Lua-C" fn broadcast_v4(&self) -> lb_ipaddr { + lb_ipaddr(Ipv4Addr::BROADCAST.into()) + } + + /// Creates an [`lb_ipaddr`] from the given input. + /// + /// If `s` is an [`lb_ipaddr`], a copy of that value is returned. If `s` is an + /// [`lb_socketaddr`], the IP address part of the socket address is returned. Otherwise, parses + /// `s` as an IP address string. Both IPv4 or IPv6 addresses are supported. + /// + /// # Errors + /// + /// Throws if `s` cannot be parsed as an IP address. + pub extern "Lua" fn ipaddr(&self, s: any) -> lb_ipaddr { + if __istype(__ct.lb_ipaddr, s) { + __new(__ct.lb_ipaddr, s) // copy constructor + } else if __istype(__ct.lb_socketaddr, s) { + s.ip() + } else { + self.__parse_ipaddr(s) + } + } + + extern "Lua-C" fn __parse_ipaddr(&self, s: &str) -> Result { + s.parse() + } + + /// Creates an [`lb_socketaddr`] from the given input. + /// + /// A socket address is an IP address with a port number. + /// + /// If `s` is an [`lb_socketaddr`], a copy of that value is returned. If `s` is an + /// [`lb_ipaddr`], a socket address with that IP address is returned. Otherwise, parses `s` as a + /// socket address string. Both IPv4 and IPv6 addresses are supported. + /// + /// If `port` is not specified, `0` is used as the default. + /// + /// # Errors + /// + /// Throws if `s` cannot be parsed as an IP or socket address. + pub extern "Lua" fn socketaddr(&self, s: any, port: any) -> lb_socketaddr { + if port != () { + self.__new_socketaddr(self.ipaddr(s), port) + } else { + if __istype(__ct.lb_socketaddr, s) { + __new(__ct.lb_socketaddr, s) // copy constructor + } else if __istype(__ct.lb_ipaddr, s) { + self.__new_socketaddr(s, 0) // default port 0 + } else { + self.__parse_socketaddr(s) + } + } + } + + extern "Lua-C" fn __new_socketaddr(&self, ip: &lb_ipaddr, port: u16) -> lb_socketaddr { + SocketAddr::new(ip.0, port).into() + } + + extern "Lua-C" fn __parse_socketaddr(&self, s: &str) -> Result { + s.parse() + } + + /// Creates a new TCP socket configured for IPv4. + /// + /// See [`TcpSocket::new_v4`]. + /// + /// # Errors + /// + /// Throws if an error was encountered during the socket creation. + pub extern "Lua" fn tcp_v4(&self) -> lb_tcpsocket { + self.__new_tcp_v4() + } + + /// Creates a new TCP socket configured for IPv6. + /// + /// See [`TcpSocket::new_v6`]. + /// + /// # Errors + /// + /// Throws if an error was encountered during the socket creation. + pub extern "Lua" fn tcp_v6(&self) -> lb_tcpsocket { + self.__new_tcp_v6() + } + + extern "Lua-C" fn __new_tcp_v4(&self) -> io::Result { + TcpSocket::new_v4().map(lb_tcpsocket) + } + + extern "Lua-C" fn __new_tcp_v6(&self) -> io::Result { + TcpSocket::new_v6().map(lb_tcpsocket) + } +} + +/// An 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 { + /// See [`IpAddr::is_unspecified`]. + pub extern "Lua-C" fn is_unspecified(&self) -> bool { + self.0.is_unspecified() + } + + /// See [`IpAddr::is_loopback`]. + pub extern "Lua-C" fn is_loopback(&self) -> bool { + self.0.is_loopback() + } + + /// See [`IpAddr::is_multicast`]. + pub extern "Lua-C" fn is_multicast(&self) -> bool { + self.0.is_multicast() + } + + /// 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 an IPv4 address. + pub extern "Lua-C" fn is_v4(&self) -> bool { + self.0.is_ipv4() + } + + /// See [`Ipv4Addr::is_private`]. + pub extern "Lua-C" fn is_v4_private(&self) -> bool { + match self.0 { + IpAddr::V4(v4) => v4.is_private(), + IpAddr::V6(_) => false, + } + } + + /// See [`Ipv4Addr::is_link_local`]. + pub extern "Lua-C" fn is_v4_link_local(&self) -> bool { + match self.0 { + IpAddr::V4(v4) => v4.is_link_local(), + IpAddr::V6(_) => false, + } + } + + /// See [`Ipv4Addr::is_broadcast`]. + pub extern "Lua-C" fn is_v4_broadcast(&self) -> bool { + match self.0 { + IpAddr::V4(v4) => v4.is_broadcast(), + IpAddr::V6(_) => false, + } + } + + /// See [`Ipv4Addr::is_documentation`]. + 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 address. + pub extern "Lua-C" fn is_v6(&self) -> bool { + self.0.is_ipv6() + } + + /// See [`Ipv6Addr::is_unique_local`]. + pub extern "Lua-C" fn is_v6_unique_local(&self) -> bool { + match self.0 { + IpAddr::V4(_) => false, + IpAddr::V6(v6) => v6.is_unique_local(), + } + } + + /// See [`Ipv6Addr::is_unicast_link_local`]. + 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(), + } + } + + /// See [`Ipv4Addr::to_ipv6_compatible`]. + pub extern "Lua-C" fn compat_v6(&self) -> Self { + match self.0 { + IpAddr::V4(v4) => Self(v4.to_ipv6_compatible().into()), + IpAddr::V6(_) => *self, + } + } + + /// See [`Ipv4Addr::to_ipv6_mapped`]. + pub extern "Lua-C" fn mapped_v6(&self) -> Self { + match self.0 { + IpAddr::V4(v4) => Self(v4.to_ipv6_mapped().into()), + IpAddr::V6(_) => *self, + } + } + + /// See [`IpAddr::to_canonical`]. + pub extern "Lua-C" fn canonical(&self) -> Self { + self.0.to_canonical().into() + } + + /// Returns the string representation of this address. + #[tostring] + pub extern "Lua-C" fn tostring(&self) -> String { + self.0.to_string() + } +} + +/// A socket address, which is an IP address with a port number. +#[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() + } + + /// Sets the IP part of this address. + /// + /// This function accepts the same arguments as [`ipaddr`](lb_libnet::ipaddr). + pub extern "Lua" fn set_ip(&mut self, s: any) -> &mut Self { + if __istype(__ct.lb_ipaddr, s) { + self.__set_ip(s); + } else if __istype(__ct.lb_socketaddr, s) { + self.__set_ip(s.ip()); + } else { + self.__set_ip_parse(s); + } + self + } + + extern "Lua-C" fn __set_ip(&mut self, ip: &lb_ipaddr) { + self.0.set_ip(ip.0); + } + + extern "Lua-C" fn __set_ip_parse(&mut self, s: &str) -> Result<(), AddrParseError> { + s.parse().map(|ip| self.0.set_ip(ip)) + } + + /// Returns the port part of this address. + pub extern "Lua-C" fn port(&self) -> u16 { + self.0.port() + } + + /// Sets the port part of this address. + pub extern "Lua" fn set_port(&mut self, port: number) -> &mut Self { + self.__set_port(port); + self + } + + extern "Lua-C" fn __set_port(&mut self, port: u16) { + self.0.set_port(port) + } + + /// Returns the string representation of this address. + #[tostring] + pub extern "Lua-C" fn tostring(&self) -> String { + self.0.to_string() + } +} + +/// A TCP socket which has not yet been converted to a [`lb_tcpstream`] or [`lb_tcplistener`]. +#[derive(Debug, From)] +#[cdef] +pub struct lb_tcpsocket(#[opaque] TcpSocket); + +#[metatype] +impl lb_tcpsocket {} + +#[derive(Debug, From)] +#[cdef] +pub struct lb_tcpstream(#[opaque] TcpStream); + +#[metatype] +impl lb_tcpstream {} + +#[derive(Debug, From)] +#[cdef] +pub struct lb_tcplistener(#[opaque] TcpListener); + +#[metatype] +impl lb_tcplistener {} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..a30429c --- /dev/null +++ b/src/lib.rs @@ -0,0 +1 @@ +pub use lb::net; diff --git a/src/main.rs b/src/main.rs index 0318ad1..ab78713 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,7 @@ use clap::Parser; -use lb_core::{GlobalState, PrettyError}; use mimalloc::MiMalloc; use owo_colors::OwoColorize; -use std::{backtrace::Backtrace, net::SocketAddr, num::NonZero, panic, thread}; -use tokio::{runtime, task::LocalSet}; +use std::{backtrace::Backtrace, net::SocketAddr, num::NonZero, panic, process::ExitCode, thread}; #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; @@ -20,14 +18,19 @@ fn panic_cb(panic: &panic::PanicHookInfo) { "unknown error" }; - eprint!( + eprintln!( + "{}:\n{trace}", + format_args!( + "thread '{}' panicked at {location}: {msg}", + thread::current().name().unwrap_or("") + ) + .red() + ); + + eprintln!( "{}", - PrettyError::new(msg) - .with_trace(trace) - .prepend(format_args!( - "thread '{}' panicked at {location}", - thread::current().name().unwrap_or("") - )) + "This is a bug in luby! Please kindly report this at https://git.lua.re/luaneko/luby." + .red() ); } @@ -76,24 +79,27 @@ impl Args { } } -fn main() { +fn main() -> ExitCode { panic::set_hook(Box::new(panic_cb)); let args = Args::parse(); init_logger(&args); - let runtime = init_runtime(&args); - GlobalState::set(init_vm(&args)); - let main = LocalSet::new(); - main.spawn_local(run(args)); - runtime.block_on(main); + let tokio = init_tokio(&args); + let lua = init_lua(&args); + let main = lua.spawn(async |s| main_async(args, s).await); + + tokio.block_on(async { + lua.await; + main.await.unwrap_or_else(|err| panic!("{err}")) + }) } fn init_logger(args: &Args) { use tracing::level_filters::LevelFilter; use tracing_subscriber::{Layer, util::*}; - let console = tracing_subscriber::fmt() + let log = tracing_subscriber::fmt() .compact() .with_env_filter( tracing_subscriber::EnvFilter::builder() @@ -110,59 +116,59 @@ fn init_logger(args: &Args) { .with_default_env() .server_addr(args.console_addr) .spawn() - .with_subscriber(console) + .with_subscriber(log) .init() } else { - console.init() + log.init() } } -fn init_runtime(args: &Args) -> runtime::Runtime { - if args.threads.get() == 1 { - runtime::Builder::new_current_thread() - } else { - runtime::Builder::new_multi_thread() - } - .enable_all() - .thread_name("lb") - .worker_threads(args.threads.get() - 1) - .max_blocking_threads(args.blocking_threads.get()) - .build() - .unwrap_or_else(|err| panic!("failed to initialise runtime: {err}")) +fn init_tokio(args: &Args) -> tokio::runtime::Runtime { + let mut rt = match args.threads.get() { + 1 => tokio::runtime::Builder::new_current_thread(), + n => { + let mut rt = tokio::runtime::Builder::new_multi_thread(); + rt.worker_threads(n - 1); + rt + } + }; + + rt.enable_all() + .thread_name("luby") + .max_blocking_threads(args.blocking_threads.get()) + .build() + .expect("failed to initialise runtime") } -fn init_vm(_args: &Args) -> luajit::State { - let mut state = - luajit::State::new().unwrap_or_else(|err| panic!("failed to initialise runtime: {err}")); - - let mut registry = luaffi::Registry::new(); - registry.preload::("lb:core"); - - println!("{registry}"); - - state - .load(&luajit::Chunk::new(registry.done()).name("@[luby]")) - .and_then(|()| state.call(0, 0)) - .unwrap_or_else(|err| panic!("failed to load modules: {err}")); - - state +fn init_lua(_args: &Args) -> lb::runtime::Runtime { + let rt = lb::runtime::Builder::new(); + rt.build().expect("failed to initialise runtime") } -async fn run(args: Args) { - let mut state = GlobalState::new_thread(); +async fn main_async(args: Args, state: &mut luajit::State) -> ExitCode { for ref path in args.paths { let chunk = match std::fs::read(path) { Ok(chunk) => chunk, - Err(err) => return eprintln!("{}", format!("{path}: {err}").red()), + Err(err) => { + eprintln!("{}", format_args!("{path}: {err}").red()); // read file error + return ExitCode::FAILURE; + } }; if let Err(err) = state.load(&luajit::Chunk::new(chunk).path(path)) { - return eprintln!("{}", err.red()); + eprintln!("{}", err.red()); // syntax error + return ExitCode::FAILURE; } - match state.call_async(0, 0).await { - Ok(_) => {} - Err(err) => GlobalState::uncaught_error(err), + if let Err(err) = state.call_async(0, 0).await { + match err.trace() { + Some(trace) => eprintln!("{}\n{trace}", err.red()), // runtime error + None => eprintln!("{}", err.red()), + } + + return ExitCode::FAILURE; } } + + ExitCode::SUCCESS }