use rustler::{Encoder, ResourceArc, Term};
use crate::{Device, TOKIO_RUNTIME, atoms};
mod atoms_serve {
rustler::atoms! {
accept,
proxy,
tcp,
http,
}
}
fn serve_config_from_erl(term: Term) -> Option<tailscale::ServeConfig> {
let tuple = rustler::types::tuple::get_tuple(term).ok()?;
if tuple.len() != 3 {
return None;
}
let name: String = tuple[0].decode().ok()?;
let port: u16 = tuple[1].decode().ok()?;
let target = serve_target_from_erl(tuple[2])?;
Some(tailscale::ServeConfig { name, port, target })
}
fn serve_target_from_erl(term: Term) -> Option<tailscale::ServeTarget> {
if let Ok(atom) = term.decode::<rustler::Atom>()
&& atom == atoms_serve::accept()
{
return Some(tailscale::ServeTarget::Accept);
}
if let Ok(tuple) = rustler::types::tuple::get_tuple(term)
&& tuple.len() == 2
&& tuple[0].decode::<rustler::Atom>().ok()? == atoms_serve::proxy()
{
let to: String = tuple[1].decode().ok()?;
return Some(tailscale::ServeTarget::Proxy { to });
}
None
}
#[rustler::nif(schedule = "DirtyIo")]
fn get_certificate(env: rustler::Env<'_>, dev: ResourceArc<Device>, name: &str) -> impl Encoder {
let dev = dev.inner.clone();
let name = name.to_owned();
match TOKIO_RUNTIME.block_on(async move { dev.get_certificate(&name).await }) {
Ok(_cert) => (atoms::ok(), atoms::ok()).encode(env),
Err(e) => (atoms::error(), e.to_string()).encode(env),
}
}
#[rustler::nif(schedule = "DirtyIo")]
fn listen_tls(env: rustler::Env<'_>, dev: ResourceArc<Device>, config: Term) -> impl Encoder {
let dev = dev.inner.clone();
let Some(cfg) = serve_config_from_erl(config) else {
return env.error_tuple("invalid serve config");
};
match TOKIO_RUNTIME.block_on(async move { dev.listen_tls(&cfg).await }) {
Ok(_acceptor) => (atoms::ok(), atoms::ok()).encode(env),
Err(e) => (atoms::error(), e.to_string()).encode(env),
}
}
#[rustler::nif(schedule = "DirtyIo")]
fn listen_funnel(
env: rustler::Env<'_>,
dev: ResourceArc<Device>,
config: Term,
funnel_only: bool,
) -> impl Encoder {
let dev = dev.inner.clone();
let Some(cfg) = serve_config_from_erl(config) else {
return env.error_tuple("invalid serve config");
};
let opts = ts_control::FunnelOptions { funnel_only };
match TOKIO_RUNTIME.block_on(async move { dev.listen_funnel(&cfg, opts).await }) {
Ok(_receiver) => (atoms::ok(), atoms::ok()).encode(env),
Err(e) => (atoms::error(), e.to_string()).encode(env),
}
}
#[rustler::nif(schedule = "DirtyIo")]
fn listen_service(
env: rustler::Env<'_>,
dev: ResourceArc<Device>,
name: &str,
mode: Term,
) -> impl Encoder {
let dev = dev.inner.clone();
let name = name.to_owned();
let Some(mode) = service_mode_from_erl(mode) else {
return env.error_tuple("invalid service mode");
};
match TOKIO_RUNTIME.block_on(async move { dev.listen_service(&name, mode).await }) {
Ok(_listener) => (atoms::ok(), atoms::ok()).encode(env),
Err(e) => (atoms::error(), e.to_string()).encode(env),
}
}
fn service_mode_from_erl(term: Term) -> Option<tailscale::ServiceMode> {
let tuple = rustler::types::tuple::get_tuple(term).ok()?;
if tuple.len() != 2 {
return None;
}
let tag = tuple[0].decode::<rustler::Atom>().ok()?;
let port: u16 = tuple[1].decode().ok()?;
if tag == atoms_serve::tcp() {
Some(tailscale::ServiceMode::Tcp { port })
} else if tag == atoms_serve::http() {
Some(tailscale::ServiceMode::Http { port })
} else {
None
}
}