#![cfg(unix)]
use std::sync::Arc;
use tokio::sync::Mutex;
use zlayer_overlayd::transport;
use zlayer_overlayd::{OverlaydClient, OverlaydServer};
use zlayer_types::overlayd::{OverlaydRequest, OverlaydResponse};
fn unique_paths() -> (std::path::PathBuf, std::path::PathBuf) {
use std::sync::atomic::{AtomicU64, Ordering};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let n = COUNTER.fetch_add(1, Ordering::SeqCst);
let stamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let base = std::env::temp_dir().join(format!(
"zlayer-overlayd-ipc-{}-{}-{}",
std::process::id(),
stamp,
n
));
let socket = base.with_extension("sock");
let data_dir = base.with_extension("data");
(socket, data_dir)
}
#[tokio::test(flavor = "multi_thread")]
async fn ipc_round_trip_over_unix_socket() {
let (socket, data_dir) = unique_paths();
let _ = std::fs::remove_file(&socket);
let _ = std::fs::remove_dir_all(&data_dir);
let server = Arc::new(Mutex::new(OverlaydServer::new(data_dir.clone())));
let serve_socket = socket.clone();
let serve_server = Arc::clone(&server);
let serve_task = tokio::spawn(async move {
let _ = transport::serve(&serve_socket, move |mut conn| {
let server = Arc::clone(&serve_server);
async move {
loop {
let Ok(frame) = conn.recv().await else {
return;
};
if let zlayer_types::overlayd::OverlaydFrame::Request { id, request } = frame {
let response = server.lock().await.handle(request).await;
if conn
.send(&zlayer_types::overlayd::OverlaydFrame::Response { id, response })
.await
.is_err()
{
return;
}
}
}
}
})
.await;
});
let mut client = OverlaydClient::connect_with_backoff(&socket)
.await
.expect("client must connect to the overlayd socket");
let resp = client
.request(OverlaydRequest::Status)
.await
.expect("Status request must round-trip");
let snap = match resp {
OverlaydResponse::Status(snap) => snap,
other => panic!("expected Status response, got {other:?}"),
};
assert!(snap.interface.is_none(), "fresh server has no interface");
assert!(snap.node_ip.is_none(), "fresh server has no node_ip");
assert!(snap.public_key.is_none(), "fresh server has no public_key");
assert_eq!(snap.peer_count, 0);
assert_eq!(snap.service_count, 0);
assert!(snap.peers.is_empty());
let resp = client
.request(OverlaydRequest::SetLocalNodeId { node_id: 7 })
.await
.expect("SetLocalNodeId must round-trip");
assert!(
matches!(resp, OverlaydResponse::Ok),
"SetLocalNodeId should return Ok, got {resp:?}"
);
let resp = client
.request(OverlaydRequest::SetLocalWgPubkey { pubkey: "k".into() })
.await
.expect("SetLocalWgPubkey must round-trip");
assert!(
matches!(resp, OverlaydResponse::Ok),
"SetLocalWgPubkey should return Ok, got {resp:?}"
);
let resp = client
.call(OverlaydRequest::SetLocalNodeId { node_id: 9 })
.await
.expect("call() must succeed for a non-error response");
assert!(matches!(resp, OverlaydResponse::Ok));
drop(client);
serve_task.abort();
let _ = std::fs::remove_file(&socket);
let _ = std::fs::remove_dir_all(&data_dir);
}