use std::collections::HashMap;
use std::path::PathBuf;
use rustler::{Atom, Encoder, ResourceArc, Term};
use tailscale::tsnet;
use crate::TOKIO_RUNTIME;
mod atoms {
rustler::atoms! {
hostname,
auth_key,
control_url,
ephemeral,
dir,
tags,
}
}
pub(crate) struct ServerResource {
inner: tsnet::Server,
}
#[rustler::resource_impl]
impl rustler::Resource for ServerResource {}
pub(crate) struct LocalClientResource {
inner: tsnet::LocalClient,
}
#[rustler::resource_impl]
impl rustler::Resource for LocalClientResource {}
#[rustler::nif]
fn server_new(env: rustler::Env<'_>, opts: HashMap<Atom, Term<'_>>) -> impl Encoder {
match build_server(&opts) {
Ok(s) => (
crate::atoms::ok(),
ResourceArc::new(ServerResource { inner: s }),
)
.encode(env),
Err(reason) => (crate::atoms::error(), reason).encode(env),
}
}
fn build_server(opts: &HashMap<Atom, Term<'_>>) -> Result<tsnet::Server, &'static str> {
let mut s = tsnet::Server::new();
if let Some(v) = opts.get(&atoms::hostname()) {
s.hostname = Some(
v.decode::<String>()
.map_err(|_| "hostname must be a string")?,
);
}
if let Some(v) = opts.get(&atoms::auth_key()) {
s.auth_key = Some(
v.decode::<String>()
.map_err(|_| "auth_key must be a string")?,
);
}
if let Some(v) = opts.get(&atoms::control_url()) {
s.control_url = Some(
v.decode::<String>()
.map_err(|_| "control_url must be a string")?,
);
}
if let Some(v) = opts.get(&atoms::ephemeral()) {
s.ephemeral = v
.decode::<bool>()
.map_err(|_| "ephemeral must be a boolean")?;
}
if let Some(v) = opts.get(&atoms::dir()) {
s.dir = Some(PathBuf::from(
v.decode::<String>().map_err(|_| "dir must be a string")?,
));
}
if let Some(v) = opts.get(&atoms::tags()) {
s.advertise_tags = v
.decode::<Vec<String>>()
.map_err(|_| "tags must be a list of strings")?;
}
Ok(s)
}
#[rustler::nif(schedule = "DirtyIo")]
fn server_loopback(env: rustler::Env<'_>, srv: ResourceArc<ServerResource>) -> impl Encoder {
match TOKIO_RUNTIME.block_on(srv.inner.loopback()) {
Ok(lb) => (
crate::atoms::ok(),
(
crate::sockaddr_to_erl(env, lb.address),
lb.proxy_cred,
crate::sockaddr_to_erl(env, lb.local_api_address),
lb.local_api_cred,
),
)
.encode(env),
Err(e) => (crate::atoms::error(), e.to_string()).encode(env),
}
}
#[rustler::nif(schedule = "DirtyIo")]
fn server_local_client(env: rustler::Env<'_>, srv: ResourceArc<ServerResource>) -> impl Encoder {
match TOKIO_RUNTIME.block_on(srv.inner.local_client()) {
Ok(lc) => (
crate::atoms::ok(),
ResourceArc::new(LocalClientResource { inner: lc }),
)
.encode(env),
Err(e) => (crate::atoms::error(), e.to_string()).encode(env),
}
}
#[rustler::nif(schedule = "DirtyIo")]
fn local_client_status(
env: rustler::Env<'_>,
lc: ResourceArc<LocalClientResource>,
) -> impl Encoder {
match TOKIO_RUNTIME.block_on(lc.inner.status()) {
Ok(body) => (
crate::atoms::ok(),
String::from_utf8_lossy(&body).into_owned(),
)
.encode(env),
Err(e) => (crate::atoms::error(), e.to_string()).encode(env),
}
}
#[rustler::nif(schedule = "DirtyIo")]
fn local_client_get(
env: rustler::Env<'_>,
lc: ResourceArc<LocalClientResource>,
path: &str,
) -> impl Encoder {
match TOKIO_RUNTIME.block_on(lc.inner.get(path)) {
Ok((code, body)) => (
crate::atoms::ok(),
(code, String::from_utf8_lossy(&body).into_owned()),
)
.encode(env),
Err(e) => (crate::atoms::error(), e.to_string()).encode(env),
}
}
#[rustler::nif]
fn local_client_address(
env: rustler::Env<'_>,
lc: ResourceArc<LocalClientResource>,
) -> impl Encoder {
crate::sockaddr_to_erl(env, lc.inner.address())
}
#[rustler::nif]
fn local_client_credential(lc: ResourceArc<LocalClientResource>) -> String {
lc.inner.credential().to_owned()
}