use std::ffi::c_char;
use crate::{keys::persisted_key_state, util};
#[derive(Default)]
#[repr(C)]
pub struct config<'a> {
pub control_server_url: *const c_char,
pub hostname: *const c_char,
pub tags: *const *const c_char,
pub client_name: *const c_char,
pub key_state: Option<&'a mut persisted_key_state>,
}
impl config<'_> {
pub unsafe fn to_ts_config(&self) -> tailscale::Config {
let mut cfg = tailscale::Config::default();
let ctrl_url = unsafe { util::str(self.control_server_url) }.and_then(|u| u.parse().ok());
if let Some(u) = ctrl_url {
cfg.control_server_url = u;
}
if let Some(hostname) = unsafe { util::str(self.hostname) } {
cfg.requested_hostname = Some(hostname.to_string());
}
cfg.client_name = Some(
unsafe { util::str(self.client_name) }
.unwrap_or("ts_ffi")
.to_owned(),
);
if let Some(key_state) = &self.key_state {
cfg.key_state = (&**key_state).into();
}
cfg.requested_tags = unsafe {
load_sentinel_array(self.tags, |&tag| {
if tag.is_null() {
return None;
};
match util::str(tag) {
Some(tag_str) => Some(Some(tag_str.to_owned())),
None => {
tracing::error!("skipping invalid requested tag");
Some(None)
}
}
})
}
.collect();
cfg
}
}
unsafe fn load_sentinel_array<'t, T, It>(
mut ary: *const T,
elem_txfm: impl Fn(&T) -> Option<It> + 't,
) -> impl Iterator<Item = It::Item>
where
T: 't,
It: IntoIterator,
{
std::iter::from_fn(move || {
if ary.is_null() {
return None;
}
let it = match elem_txfm(unsafe { ary.as_ref().unwrap() }) {
Some(u) => u,
None => {
return None;
}
};
ary = unsafe { ary.offset(1) };
Some(it)
})
.flatten()
}
#[cfg(test)]
mod test {
use std::{ffi::CString, ptr::null};
use super::*;
#[test]
fn sentinel_array() {
let mut v = unsafe { load_sentinel_array::<u8, _>(null(), |_| Option::<[u8; 1]>::None) };
assert!(v.next().is_none());
let ary = [0u8, 1, 2, 3, 4, 5, 6, 128, 32];
let mut v =
unsafe { load_sentinel_array(&ary as *const u8, |_elt| Option::<Option<u8>>::None) };
assert!(v.next().is_none());
let v = unsafe {
load_sentinel_array(
&ary as *const u8,
|&elt| {
if elt < 10 { Some([elt]) } else { None }
},
)
}
.collect::<Vec<_>>();
assert!(!v.is_empty());
assert_eq!(v, ary[..=6].to_vec());
}
#[test]
fn tags() {
let tag_foo = CString::new("foo").unwrap();
let tag_bar = CString::new("bar").unwrap();
let config = config {
tags: &[tag_foo.as_ptr(), tag_bar.as_ptr(), null()] as *const *const c_char,
..Default::default()
};
let cfg = unsafe { config.to_ts_config() };
assert_eq!(cfg.requested_tags, vec!["foo", "bar"]);
}
}