confium_net/transports/
inproc.rs1use std::collections::HashMap;
15use std::sync::Mutex;
16use std::sync::OnceLock;
17use std::sync::mpsc;
18
19use url::Url;
20
21use crate::Listener;
22use crate::Result;
23use crate::Transport;
24use crate::error::ClosedSnafu;
25use crate::error::MalformedUrlSnafu;
26use crate::registry::TransportKind;
27
28static LISTENERS: OnceLock<Mutex<HashMap<String, mpsc::Sender<InprocHandshake>>>> = OnceLock::new();
34
35fn listeners() -> &'static Mutex<HashMap<String, mpsc::Sender<InprocHandshake>>> {
36 LISTENERS.get_or_init(|| Mutex::new(HashMap::new()))
37}
38
39struct InprocHandshake {
43 connector_to_peer: mpsc::Receiver<Vec<u8>>,
46 peer_to_connector: mpsc::Sender<Vec<u8>>,
49}
50
51fn channel_name(url: &Url) -> Result<String> {
53 let name = url.host_str().unwrap_or("");
54 if name.is_empty() {
55 return MalformedUrlSnafu {
56 scheme: "inproc",
57 url: url.to_string(),
58 reason: "missing channel name (use inproc://<name>)",
59 }
60 .fail();
61 }
62 Ok(name.to_string())
63}
64
65pub struct InprocTransport {
69 tx: Option<mpsc::Sender<Vec<u8>>>,
70 rx: Option<mpsc::Receiver<Vec<u8>>>,
71 pending: Vec<u8>,
74}
75
76impl InprocTransport {
77 fn new(tx: mpsc::Sender<Vec<u8>>, rx: mpsc::Receiver<Vec<u8>>) -> Self {
78 Self {
79 tx: Some(tx),
80 rx: Some(rx),
81 pending: Vec::new(),
82 }
83 }
84}
85
86impl Transport for InprocTransport {
87 fn send(&mut self, data: &[u8]) -> Result<()> {
88 match &self.tx {
89 Some(tx) => tx.send(data.to_vec()).map_err(|_| ClosedSnafu.build()),
90 None => ClosedSnafu.fail(),
91 }
92 }
93
94 fn recv(&mut self, buf: &mut [u8]) -> Result<usize> {
95 if self.pending.is_empty() {
96 let rx = match &self.rx {
97 Some(rx) => rx,
98 None => return ClosedSnafu.fail(),
99 };
100 let msg = rx.recv().map_err(|_| ClosedSnafu.build())?;
101 self.pending = msg;
102 }
103 let n = std::cmp::min(self.pending.len(), buf.len());
104 buf[..n].copy_from_slice(&self.pending[..n]);
105 self.pending.drain(..n);
112 Ok(n)
113 }
114
115 fn close(&mut self) -> Result<()> {
116 self.tx = None;
117 self.rx = None;
118 self.pending.clear();
119 Ok(())
120 }
121}
122
123pub struct InprocListener {
128 name: String,
129 inbox: Option<mpsc::Receiver<InprocHandshake>>,
130}
131
132impl InprocListener {
133 fn new(name: String, inbox: mpsc::Receiver<InprocHandshake>) -> Self {
134 Self {
135 name,
136 inbox: Some(inbox),
137 }
138 }
139}
140
141impl Listener for InprocListener {
142 fn accept(&mut self) -> Result<Box<dyn Transport>> {
143 let inbox = match &self.inbox {
144 Some(rx) => rx,
145 None => return ClosedSnafu.fail(),
146 };
147 let handshake = inbox.recv().map_err(|_| ClosedSnafu.build())?;
148 Ok(Box::new(InprocTransport::new(
149 handshake.peer_to_connector,
150 handshake.connector_to_peer,
151 )))
152 }
153}
154
155impl Drop for InprocListener {
156 fn drop(&mut self) {
157 if let Ok(mut table) = listeners().lock() {
158 table.remove(&self.name);
159 }
160 }
161}
162
163pub struct InprocKind;
165
166impl TransportKind for InprocKind {
167 fn schemes(&self) -> &'static [&'static str] {
168 &["inproc"]
169 }
170
171 fn connect(&self, url: &Url) -> Result<Box<dyn Transport>> {
172 let name = channel_name(url)?;
173 let tx = {
174 let mut table = listeners().lock().expect("inproc listener table poisoned");
175 match table.remove(&name) {
176 Some(sender) => sender,
177 None => {
178 return MalformedUrlSnafu {
179 scheme: "inproc",
180 url: url.to_string(),
181 reason: "no listener registered for this channel name",
182 }
183 .fail();
184 }
185 }
186 };
187 let (connector_to_peer_tx, connector_to_peer_rx) = mpsc::channel::<Vec<u8>>();
188 let (peer_to_connector_tx, peer_to_connector_rx) = mpsc::channel::<Vec<u8>>();
189 tx.send(InprocHandshake {
190 connector_to_peer: connector_to_peer_rx,
191 peer_to_connector: peer_to_connector_tx,
192 })
193 .map_err(|_| ClosedSnafu.build())?;
194 Ok(Box::new(InprocTransport::new(
195 connector_to_peer_tx,
196 peer_to_connector_rx,
197 )))
198 }
199
200 fn listen(&self, url: &Url) -> Result<Box<dyn Listener>> {
201 let name = channel_name(url)?;
202 let (handshake_tx, handshake_rx) = mpsc::channel::<InprocHandshake>();
203 let mut table = listeners().lock().expect("inproc listener table poisoned");
204 if table.contains_key(&name) {
205 return MalformedUrlSnafu {
206 scheme: "inproc",
207 url: url.to_string(),
208 reason: "channel name already in use",
209 }
210 .fail();
211 }
212 table.insert(name.clone(), handshake_tx);
213 Ok(Box::new(InprocListener::new(name, handshake_rx)))
214 }
215}
216
217crate::register_transport!(InprocKind);
218
219#[cfg(test)]
220mod tests {
221 fn unique_name(tag: &str) -> String {
225 use std::sync::atomic::AtomicU64;
226 use std::sync::atomic::Ordering;
227 static SEQ: AtomicU64 = AtomicU64::new(0);
228 let n = SEQ.fetch_add(1, Ordering::Relaxed);
229 format!("test-{tag}-{n}")
230 }
231
232 #[test]
233 fn round_trip_single_message() {
234 let name = unique_name("rt");
235 let url = format!("inproc://{name}");
236 let mut listener = crate::listen(&url).unwrap();
237 let mut client = crate::connect(&url).unwrap();
239 let mut server = listener.accept().unwrap();
240
241 let payload = b"hello threshold world";
242 client.send(payload).unwrap();
243 let mut buf = [0u8; 64];
244 let n = server.recv(&mut buf).unwrap();
245 assert_eq!(&buf[..n], payload);
246 }
247
248 #[test]
249 fn bidirectional_traffic() {
250 let name = unique_name("bi");
251 let url = format!("inproc://{name}");
252 let mut listener = crate::listen(&url).unwrap();
253 let mut client = crate::connect(&url).unwrap();
254 let mut server = listener.accept().unwrap();
255
256 client.send(b"c2s").unwrap();
257 server.send(b"s2c").unwrap();
258
259 let mut buf = [0u8; 16];
260 let n = server.recv(&mut buf).unwrap();
261 assert_eq!(&buf[..n], b"c2s");
262 let n = client.recv(&mut buf).unwrap();
263 assert_eq!(&buf[..n], b"s2c");
264 }
265
266 #[test]
267 fn recv_after_close_reports_closed() {
268 let name = unique_name("close");
269 let url = format!("inproc://{name}");
270 let mut listener = crate::listen(&url).unwrap();
271 let mut client = crate::connect(&url).unwrap();
272 let mut server = listener.accept().unwrap();
273
274 client.close().unwrap();
275 let mut buf = [0u8; 16];
276 let err = server.recv(&mut buf).unwrap_err();
277 assert!(matches!(err, crate::error::Error::Closed { .. }));
278 }
279
280 #[test]
281 fn connect_without_listener_fails() {
282 let url = "inproc://no-such-channel-zzz";
283 let err = crate::connect(url).err().unwrap();
284 assert!(matches!(
285 err,
286 crate::error::Error::MalformedUrl { scheme, .. } if scheme == "inproc"
287 ));
288 }
289
290 #[test]
291 fn listen_twice_same_name_collides() {
292 let name = unique_name("collide");
293 let url = format!("inproc://{name}");
294 let _l1 = crate::listen(&url).unwrap();
295 let err = crate::listen(&url).err().unwrap();
296 assert!(matches!(
297 err,
298 crate::error::Error::MalformedUrl { scheme, reason, .. }
299 if scheme == "inproc" && reason.contains("already in use")
300 ));
301 }
302}