pamoja_loopback/
transport.rs1use std::sync::{Arc, Mutex};
4
5use tokio::sync::mpsc::{self, UnboundedReceiver};
6
7use pamoja_core::{Error, Message, Receive, Result, Transport};
8
9use crate::broker::LoopbackBroker;
10
11pub struct LoopbackTransport {
40 broker: LoopbackBroker,
41 filters: Arc<Mutex<Vec<String>>>,
42 incoming: Option<UnboundedReceiver<Message>>,
43}
44
45impl LoopbackTransport {
46 pub fn new(broker: LoopbackBroker) -> Self {
56 Self {
57 broker,
58 filters: Arc::new(Mutex::new(Vec::new())),
59 incoming: None,
60 }
61 }
62
63 pub fn is_connected(&self) -> bool {
70 self.incoming.is_some()
71 }
72
73 pub fn disconnect(&mut self) {
79 self.incoming = None;
80 }
81}
82
83impl Transport for LoopbackTransport {
84 async fn connect(&mut self) -> Result<()> {
85 self.broker.in_reach()?;
86 let (sender, receiver) = mpsc::unbounded_channel();
87 self.broker.register(Arc::clone(&self.filters), sender);
88 self.incoming = Some(receiver);
89 Ok(())
90 }
91
92 async fn send(&mut self, topic: &str, payload: &[u8]) -> Result<()> {
93 if self.incoming.is_none() {
94 return Err(Error::Closed);
95 }
96 self.broker.in_reach()?;
97 self.broker.publish(&Message::new(topic, payload));
98 Ok(())
99 }
100
101 async fn subscribe(&mut self, topic: &str) -> Result<()> {
102 if self.incoming.is_none() {
103 return Err(Error::Closed);
104 }
105 self.broker.in_reach()?;
106 self.filters
107 .lock()
108 .expect("filters lock")
109 .push(topic.to_owned());
110 Ok(())
111 }
112}
113
114impl Receive for LoopbackTransport {
115 async fn recv(&mut self) -> Result<Option<Message>> {
130 let incoming = self.incoming.as_mut().ok_or(Error::Closed)?;
131 Ok(incoming.recv().await)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 #[tokio::test]
140 async fn publish_and_subscribe_round_trip() {
141 let broker = LoopbackBroker::new();
142 let mut subscriber = LoopbackTransport::new(broker.clone());
143 let mut publisher = LoopbackTransport::new(broker);
144 subscriber.connect().await.expect("connect");
145 publisher.connect().await.expect("connect");
146
147 subscriber
148 .subscribe("sensors/+/temperature")
149 .await
150 .expect("subscribe");
151 publisher
152 .send("sensors/1/temperature", b"21.5")
153 .await
154 .expect("send");
155
156 let message = subscriber.recv().await.expect("recv").expect("a message");
157 assert_eq!(message.topic, "sensors/1/temperature");
158 assert_eq!(message.payload, b"21.5");
159 }
160
161 #[tokio::test]
162 async fn non_matching_topics_are_not_delivered() {
163 let broker = LoopbackBroker::new();
164 let mut subscriber = LoopbackTransport::new(broker.clone());
165 let mut publisher = LoopbackTransport::new(broker);
166 subscriber.connect().await.expect("connect");
167 publisher.connect().await.expect("connect");
168
169 subscriber
170 .subscribe("sensors/1/#")
171 .await
172 .expect("subscribe");
173 publisher
174 .send("sensors/2/temperature", b"x")
175 .await
176 .expect("send");
177 publisher
178 .send("sensors/1/humidity", b"y")
179 .await
180 .expect("send");
181
182 let message = subscriber.recv().await.expect("recv").expect("a message");
183 assert_eq!(message.topic, "sensors/1/humidity");
184 }
185
186 #[tokio::test]
187 async fn a_connected_link_waits_for_a_message_rather_than_ending() {
188 use std::future::Future;
189 use std::task::{Context, Waker};
190
191 let mut link = LoopbackTransport::new(LoopbackBroker::new());
192 link.connect().await.expect("connect");
193 link.subscribe("t").await.expect("subscribe");
194 {
195 let mut waiting = std::pin::pin!(link.recv());
196 let mut context = Context::from_waker(Waker::noop());
197 assert!(waiting.as_mut().poll(&mut context).is_pending());
198 }
199
200 link.send("t", b"1").await.expect("send");
201 let message = link.recv().await.expect("recv").expect("a message");
202 assert_eq!(message.payload, b"1", "a link hears its own publishes");
203 }
204
205 #[tokio::test]
206 async fn a_message_matching_two_filters_arrives_once() {
207 let broker = LoopbackBroker::new();
208 let mut subscriber = LoopbackTransport::new(broker.clone());
209 let mut publisher = LoopbackTransport::new(broker);
210 subscriber.connect().await.expect("connect");
211 publisher.connect().await.expect("connect");
212 subscriber.subscribe("a/+").await.expect("subscribe");
213 subscriber.subscribe("a/#").await.expect("subscribe");
214
215 publisher.send("a/b", b"1").await.expect("send");
216 publisher.send("a/c", b"2").await.expect("send");
217 let first = subscriber.recv().await.expect("recv").expect("a message");
218 let second = subscriber.recv().await.expect("recv").expect("a message");
219 assert_eq!(first.topic, "a/b");
220 assert_eq!(second.topic, "a/c");
221 }
222
223 #[tokio::test]
224 async fn a_subscription_hears_only_what_is_published_after_it() {
225 let broker = LoopbackBroker::new();
226 let mut subscriber = LoopbackTransport::new(broker.clone());
227 let mut publisher = LoopbackTransport::new(broker);
228 subscriber.connect().await.expect("connect");
229 publisher.connect().await.expect("connect");
230
231 publisher.send("t", b"before").await.expect("send");
232 subscriber.subscribe("t").await.expect("subscribe");
233 publisher.send("t", b"after").await.expect("send");
234 let message = subscriber.recv().await.expect("recv").expect("a message");
235 assert_eq!(message.payload, b"after");
236 }
237
238 #[tokio::test]
239 async fn a_reconnected_link_keeps_its_filters_and_drops_what_was_queued() {
240 let broker = LoopbackBroker::new();
241 let mut subscriber = LoopbackTransport::new(broker.clone());
242 let mut publisher = LoopbackTransport::new(broker);
243 subscriber.connect().await.expect("connect");
244 publisher.connect().await.expect("connect");
245 subscriber.subscribe("t").await.expect("subscribe");
246
247 publisher.send("t", b"queued").await.expect("send");
248 subscriber.disconnect();
249 publisher.send("t", b"missed").await.expect("send");
250 subscriber.connect().await.expect("reconnect");
251 publisher.send("t", b"heard").await.expect("send");
252 let message = subscriber.recv().await.expect("recv").expect("a message");
253 assert_eq!(message.payload, b"heard");
254 }
255
256 #[tokio::test]
257 async fn links_on_separate_brokers_never_hear_each_other() {
258 let mut subscriber = LoopbackTransport::new(LoopbackBroker::new());
259 let mut publisher = LoopbackTransport::new(LoopbackBroker::new());
260 subscriber.connect().await.expect("connect");
261 publisher.connect().await.expect("connect");
262 subscriber.subscribe("t").await.expect("subscribe");
263 publisher.send("t", b"elsewhere").await.expect("send");
264 subscriber.send("t", b"here").await.expect("send");
265 let message = subscriber.recv().await.expect("recv").expect("a message");
266 assert_eq!(message.payload, b"here");
267 }
268
269 #[tokio::test]
270 async fn an_unreachable_broker_refuses_its_links_until_it_is_back() {
271 let broker = LoopbackBroker::new();
272 let mut listener = LoopbackTransport::new(broker.clone());
273 let mut node = LoopbackTransport::new(broker.clone());
274 listener.connect().await.expect("connect");
275 node.connect().await.expect("connect");
276 listener.subscribe("t").await.expect("subscribe");
277
278 broker.set_reachable(false);
279 assert!(!broker.is_reachable());
280 for refused in [
281 node.send("t", b"lost").await,
282 node.subscribe("u").await,
283 LoopbackTransport::new(broker.clone()).connect().await,
284 ] {
285 match refused {
286 Err(Error::Transport(reason)) => assert_eq!(reason, "the broker is out of reach"),
287 other => panic!("an unreachable broker allowed {other:?}"),
288 }
289 }
290 assert!(node.is_connected(), "an outage keeps the connection");
291
292 broker.set_reachable(true);
293 node.send("t", b"back").await.expect("send");
294 let message = listener.recv().await.expect("recv").expect("a message");
295 assert_eq!(
296 message.payload, b"back",
297 "nothing sent during the outage arrives"
298 );
299 }
300
301 #[tokio::test]
302 async fn operations_before_connect_report_closed() {
303 let broker = LoopbackBroker::new();
304 let mut transport = LoopbackTransport::new(broker);
305 assert!(matches!(
306 transport.send("t", b"x").await,
307 Err(Error::Closed)
308 ));
309 assert!(matches!(transport.subscribe("t").await, Err(Error::Closed)));
310 assert!(matches!(transport.recv().await, Err(Error::Closed)));
311 assert!(!transport.is_connected());
312 }
313}