use super::{TransportError, TransportResult, MAX_MESSAGE_SIZE};
use std::path::PathBuf;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
pub struct UnixSocketConnection {
stream: tokio::net::UnixStream,
}
impl UnixSocketConnection {
pub fn from_stream(stream: tokio::net::UnixStream) -> Self {
Self { stream }
}
pub async fn connect(path: PathBuf) -> TransportResult<Self> {
let stream = tokio::net::UnixStream::connect(&path).await.map_err(|e| {
TransportError::ConnectionFailed(format!(
"Failed to connect to {}: {}",
path.display(),
e
))
})?;
Ok(Self { stream })
}
pub async fn read_message(&mut self) -> TransportResult<Vec<u8>> {
let mut length_buf = [0u8; 4];
self.stream.read_exact(&mut length_buf).await?;
let length = u32::from_be_bytes(length_buf) as usize;
if length == 0 || length > MAX_MESSAGE_SIZE {
return Err(TransportError::MessageTooLarge {
size: length,
max: MAX_MESSAGE_SIZE,
});
}
let mut buffer = vec![0u8; length];
self.stream.read_exact(&mut buffer).await?;
Ok(buffer)
}
pub async fn write_message(&mut self, data: &[u8]) -> TransportResult<()> {
let length = data.len() as u32;
if length as usize > MAX_MESSAGE_SIZE {
return Err(TransportError::MessageTooLarge {
size: data.len(),
max: MAX_MESSAGE_SIZE,
});
}
self.stream.write_all(&length.to_be_bytes()).await?;
self.stream.write_all(data).await?;
self.stream.flush().await?;
Ok(())
}
pub async fn close(&mut self) -> TransportResult<()> {
self.stream.shutdown().await?;
Ok(())
}
pub fn is_open(&self) -> bool {
self.stream.peer_addr().is_ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[tokio::test]
async fn test_unix_socket_connection_roundtrip() {
let temp_dir = std::env::temp_dir();
let socket_path = temp_dir.join(format!("test_ipc_{}.sock", uuid_v4()));
let listener = tokio::net::UnixListener::bind(&socket_path).unwrap();
let server_handle = tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let mut conn = UnixSocketConnection::from_stream(stream);
let msg = conn.read_message().await.unwrap();
conn.write_message(&msg).await.unwrap();
conn.close().await.unwrap();
});
tokio::time::sleep(Duration::from_millis(100)).await;
let mut client = UnixSocketConnection::connect(socket_path).await.unwrap();
let test_data = b"Hello, IPC!";
client.write_message(test_data).await.unwrap();
let received = client.read_message().await.unwrap();
assert_eq!(received, test_data);
server_handle.await.unwrap();
}
fn uuid_v4() -> String {
let nanos = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
format!("proto{n}", n = nanos)
}
}