mq_bridge/endpoints/memory/
ipc_unix.rs1#![cfg(unix)]
7
8use super::transport::TransportChannel;
9use crate::CanonicalMessage;
10use anyhow::{anyhow, Result};
11use async_trait::async_trait;
12use std::path::Path;
13use std::sync::Arc;
14use tokio::io::{AsyncReadExt, AsyncWriteExt};
15use tokio::net::{UnixListener, UnixStream};
16use tokio::sync::Mutex;
17use tracing::{debug, info, warn};
18
19#[derive(Clone)]
21pub struct UnixIpcTransport {
22 inner: Arc<UnixIpcTransportInner>,
23}
24
25struct UnixIpcTransportInner {
26 socket_path: String,
27 capacity: usize,
28 listener: Mutex<Option<UnixListener>>,
30 stream: Mutex<Option<UnixStream>>,
32 closed: Mutex<bool>,
33}
34
35impl UnixIpcTransport {
36 pub async fn new_server(socket_path: impl AsRef<Path>, capacity: usize) -> Result<Self> {
38 let socket_path = socket_path.as_ref();
39
40 if socket_path.exists() {
42 std::fs::remove_file(socket_path)?;
43 }
44
45 if let Some(parent) = socket_path.parent() {
47 std::fs::create_dir_all(parent)?;
48 #[cfg(unix)]
50 {
51 use std::os::unix::fs::PermissionsExt;
52 let mut perms = std::fs::metadata(parent)?.permissions();
53 perms.set_mode(0o700);
54 std::fs::set_permissions(parent, perms)?;
55 }
56 }
57
58 let listener = UnixListener::bind(socket_path)?;
59
60 #[cfg(unix)]
62 {
63 use std::os::unix::fs::PermissionsExt;
64 let mut perms = std::fs::metadata(socket_path)?.permissions();
65 perms.set_mode(0o600);
66 std::fs::set_permissions(socket_path, perms)?;
67 }
68
69 info!(path = %socket_path.display(), "Unix IPC server listening");
70
71 Ok(Self {
72 inner: Arc::new(UnixIpcTransportInner {
73 socket_path: socket_path.to_string_lossy().to_string(),
74 capacity,
75 listener: Mutex::new(Some(listener)),
76 stream: Mutex::new(None),
77 closed: Mutex::new(false),
78 }),
79 })
80 }
81
82 pub async fn new_client(socket_path: impl AsRef<Path>, capacity: usize) -> Result<Self> {
84 let socket_path = socket_path.as_ref();
85
86 let stream = UnixStream::connect(socket_path).await?;
87
88 info!(path = %socket_path.display(), "Unix IPC client connected");
89
90 Ok(Self {
91 inner: Arc::new(UnixIpcTransportInner {
92 socket_path: socket_path.to_string_lossy().to_string(),
93 capacity,
94 listener: Mutex::new(None),
95 stream: Mutex::new(Some(stream)),
96 closed: Mutex::new(false),
97 }),
98 })
99 }
100
101 async fn send_frame(stream: &mut UnixStream, data: &[u8]) -> Result<()> {
103 let len = data.len() as u32;
104 stream.write_all(&len.to_be_bytes()).await?;
105 stream.write_all(data).await?;
106 stream.flush().await?;
107 Ok(())
108 }
109
110 async fn recv_frame(stream: &mut UnixStream) -> Result<Vec<u8>> {
112 let mut len_bytes = [0u8; 4];
113 stream.read_exact(&mut len_bytes).await?;
114 let len = u32::from_be_bytes(len_bytes) as usize;
115
116 if len > 100 * 1024 * 1024 {
118 return Err(anyhow!("Frame too large: {} bytes", len));
119 }
120
121 let mut data = vec![0u8; len];
122 stream.read_exact(&mut data).await?;
123 Ok(data)
124 }
125
126 async fn accept_connection(&self) -> Result<UnixStream> {
128 let listener_guard = self.inner.listener.lock().await;
129 if let Some(listener) = listener_guard.as_ref() {
130 let (stream, _addr) = listener.accept().await?;
131 debug!(path = %self.inner.socket_path, "Accepted Unix IPC connection");
132 Ok(stream)
133 } else {
134 Err(anyhow!("Unix IPC transport not in server mode"))
135 }
136 }
137
138 fn is_disconnected(error: &anyhow::Error) -> bool {
139 error
140 .downcast_ref::<std::io::Error>()
141 .is_some_and(|io_error| {
142 matches!(
143 io_error.kind(),
144 std::io::ErrorKind::UnexpectedEof
145 | std::io::ErrorKind::BrokenPipe
146 | std::io::ErrorKind::ConnectionReset
147 | std::io::ErrorKind::ConnectionAborted
148 | std::io::ErrorKind::NotConnected
149 )
150 })
151 }
152}
153
154#[async_trait]
155impl TransportChannel for UnixIpcTransport {
156 async fn send_batch(&self, messages: Vec<CanonicalMessage>) -> Result<()> {
157 if *self.inner.closed.lock().await {
158 return Err(anyhow!("Unix IPC transport is closed"));
159 }
160
161 let data = rmp_serde::to_vec(&messages)?;
163
164 let mut stream_guard = self.inner.stream.lock().await;
165 if let Some(stream) = stream_guard.as_mut() {
166 Self::send_frame(stream, &data).await?;
167 debug!(
168 path = %self.inner.socket_path,
169 count = messages.len(),
170 bytes = data.len(),
171 "Sent batch via Unix IPC"
172 );
173 Ok(())
174 } else {
175 Err(anyhow!("Unix IPC transport not in client mode"))
176 }
177 }
178
179 async fn recv_batch(&self) -> Result<Vec<CanonicalMessage>> {
180 loop {
181 if *self.inner.closed.lock().await {
182 return Err(anyhow!("Unix IPC transport is closed"));
183 }
184
185 let mut stream_guard = self.inner.stream.lock().await;
186 if stream_guard.is_none() {
187 drop(stream_guard);
188 let stream = self.accept_connection().await?;
189 stream_guard = self.inner.stream.lock().await;
190 *stream_guard = Some(stream);
191 }
192
193 let read_result = if let Some(stream) = stream_guard.as_mut() {
194 Self::recv_frame(stream).await
195 } else {
196 Err(anyhow!("Unix IPC transport has no active connection"))
197 };
198
199 match read_result {
200 Ok(data) => {
201 let messages: Vec<CanonicalMessage> = rmp_serde::from_slice(&data)?;
202 debug!(
203 path = %self.inner.socket_path,
204 count = messages.len(),
205 bytes = data.len(),
206 "Received batch via Unix IPC"
207 );
208 return Ok(messages);
209 }
210 Err(error) if Self::is_disconnected(&error) => {
211 warn!(path = %self.inner.socket_path, error = %error, "Unix IPC peer disconnected; waiting for a new connection");
212 *stream_guard = None;
213 }
214 Err(error) => return Err(error),
215 }
216 }
217 }
218
219 fn try_recv_batch(&self) -> Result<Option<Vec<CanonicalMessage>>> {
220 Ok(None)
224 }
225
226 fn len(&self) -> usize {
227 0
230 }
231
232 fn capacity(&self) -> Option<usize> {
233 Some(self.inner.capacity)
234 }
235
236 fn is_closed(&self) -> bool {
237 if let Ok(closed) = self.inner.closed.try_lock() {
239 *closed
240 } else {
241 false
242 }
243 }
244
245 fn close(&self) {
246 if let Ok(mut closed) = self.inner.closed.try_lock() {
247 *closed = true;
248 info!(path = %self.inner.socket_path, "Closing Unix IPC transport");
249 }
250 }
251}
252
253impl Drop for UnixIpcTransport {
254 fn drop(&mut self) {
255 if let Ok(listener_guard) = self.inner.listener.try_lock() {
257 if listener_guard.is_some() {
258 let socket_path = &self.inner.socket_path;
259 if let Err(e) = std::fs::remove_file(socket_path) {
260 if e.kind() != std::io::ErrorKind::NotFound {
261 warn!(path = %socket_path, error = %e, "Failed to remove Unix socket file");
262 }
263 }
264 }
265 }
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use tempfile::TempDir;
273
274 #[tokio::test]
275 async fn test_unix_ipc_roundtrip() {
276 let temp_dir = TempDir::new().unwrap();
277 let socket_path = temp_dir.path().join("test.sock");
278
279 let server = UnixIpcTransport::new_server(&socket_path, 10)
281 .await
282 .unwrap();
283
284 let client = UnixIpcTransport::new_client(&socket_path, 10)
286 .await
287 .unwrap();
288
289 let msg = CanonicalMessage::from_vec(b"test");
291 client.send_batch(vec![msg.clone()]).await.unwrap();
292
293 let received = server.recv_batch().await.unwrap();
295 assert_eq!(received.len(), 1);
296 }
297
298 #[tokio::test]
299 async fn test_unix_ipc_close() {
300 let temp_dir = TempDir::new().unwrap();
301 let socket_path = temp_dir.path().join("test.sock");
302
303 let server = UnixIpcTransport::new_server(&socket_path, 10)
304 .await
305 .unwrap();
306
307 assert!(!server.is_closed());
308 server.close();
309 assert!(server.is_closed());
310 }
311}
312
313