use rustler::{Encoder, ResourceArc, Term};
use crate::{Device, NodeInfo, TOKIO_RUNTIME, atoms, ip_to_erl, sockaddr_from_erl};
#[derive(rustler::NifStruct)]
#[module = "Tailscale.StatusNode"]
struct StatusNode<'a> {
stable_id: String,
display_name: String,
ipv4: Term<'a>,
ipv6: Term<'a>,
online: Option<bool>,
allowed_routes: Vec<String>,
is_exit_node: bool,
}
impl<'a> StatusNode<'a> {
fn from_node(env: rustler::Env<'a>, value: tailscale::StatusNode) -> Self {
Self {
stable_id: value.stable_id.0,
display_name: value.display_name,
ipv4: ip_to_erl(env, value.ipv4),
ipv6: ip_to_erl(env, value.ipv6),
online: value.online,
allowed_routes: value
.allowed_routes
.iter()
.map(ToString::to_string)
.collect(),
is_exit_node: value.is_exit_node,
}
}
}
#[derive(rustler::NifStruct)]
#[module = "Tailscale.Status"]
struct Status<'a> {
self_node: Option<StatusNode<'a>>,
peers: Vec<StatusNode<'a>>,
}
impl<'a> Status<'a> {
fn from_status(env: rustler::Env<'a>, value: tailscale::Status) -> Self {
Self {
self_node: value.self_node.map(|n| StatusNode::from_node(env, n)),
peers: value
.peers
.into_iter()
.map(|n| StatusNode::from_node(env, n))
.collect(),
}
}
}
#[derive(rustler::NifStruct)]
#[module = "Tailscale.WhoIs"]
struct WhoIs<'a> {
node: NodeInfo<'a>,
user: Option<String>,
capabilities: Vec<Term<'a>>,
}
impl<'a> WhoIs<'a> {
fn from_whois(env: rustler::Env<'a>, value: tailscale::WhoIs) -> Self {
Self {
node: NodeInfo::from_node(env, value.node),
user: value.user,
capabilities: value
.capabilities
.into_iter()
.map(|(cap, args)| (cap, args).encode(env))
.collect(),
}
}
}
#[rustler::nif(schedule = "DirtyIo")]
fn status(env: rustler::Env<'_>, dev: ResourceArc<Device>) -> impl Encoder {
let dev = dev.inner.clone();
match TOKIO_RUNTIME.block_on(async move { dev.status().await }) {
Err(e) => (atoms::error(), e.to_string()).encode(env),
Ok(status) => (atoms::ok(), Status::from_status(env, status)).encode(env),
}
}
#[rustler::nif(schedule = "DirtyIo")]
fn whois(env: rustler::Env<'_>, dev: ResourceArc<Device>, addr: Term) -> impl Encoder {
let dev = dev.inner.clone();
let Some(sockaddr) = sockaddr_from_erl(addr) else {
return env.error_tuple("invalid sockaddr");
};
match TOKIO_RUNTIME.block_on(async move { dev.whois(sockaddr).await }) {
Err(e) => (atoms::error(), e.to_string()).encode(env),
Ok(None) => (atoms::ok(), Option::<()>::None).encode(env),
Ok(Some(whois)) => (atoms::ok(), WhoIs::from_whois(env, whois)).encode(env),
}
}
#[rustler::nif(schedule = "DirtyIo")]
fn netmap(env: rustler::Env<'_>, dev: ResourceArc<Device>) -> impl Encoder {
let dev = dev.inner.clone();
match TOKIO_RUNTIME.block_on(async move {
let rx = dev.watch_netmap().await?;
Ok::<_, tailscale::Error>(rx.borrow().clone())
}) {
Err(e) => (atoms::error(), e.to_string()).encode(env),
Ok(nodes) => (
atoms::ok(),
nodes
.into_iter()
.map(|n| StatusNode::from_node(env, n))
.collect::<Vec<_>>(),
)
.encode(env),
}
}