Implement basic net module
This commit is contained in:
parent
122ef04b16
commit
8c47987a45
@ -1,2 +1,4 @@
|
||||
pub mod rt;
|
||||
pub mod channel;
|
||||
pub mod net;
|
||||
pub mod runtime;
|
||||
pub mod task;
|
||||
|
345
crates/lb/src/net.rs
Normal file
345
crates/lb/src/net.rs
Normal file
@ -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<lb_ipaddr, AddrParseError> {
|
||||
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<lb_socketaddr, AddrParseError> {
|
||||
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<lb_tcpsocket> {
|
||||
TcpSocket::new_v4().map(lb_tcpsocket)
|
||||
}
|
||||
|
||||
extern "Lua-C" fn __new_tcp_v6(&self) -> io::Result<lb_tcpsocket> {
|
||||
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 {}
|
1
src/lib.rs
Normal file
1
src/lib.rs
Normal file
@ -0,0 +1 @@
|
||||
pub use lb::net;
|
112
src/main.rs
112
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("<unnamed>")
|
||||
)
|
||||
.red()
|
||||
);
|
||||
|
||||
eprintln!(
|
||||
"{}",
|
||||
PrettyError::new(msg)
|
||||
.with_trace(trace)
|
||||
.prepend(format_args!(
|
||||
"thread '{}' panicked at {location}",
|
||||
thread::current().name().unwrap_or("<unnamed>")
|
||||
))
|
||||
"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::lb_core>("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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user