pamoja_loopback/
broker.rs1use std::sync::{Arc, Mutex};
4
5use tokio::sync::mpsc::UnboundedSender;
6
7use crate::transport::Message;
8
9#[derive(Clone, Default)]
16pub struct LoopbackBroker {
17 subscriptions: Arc<Mutex<Vec<Subscription>>>,
18}
19
20struct Subscription {
22 filters: Arc<Mutex<Vec<String>>>,
23 sender: UnboundedSender<Message>,
24}
25
26impl LoopbackBroker {
27 pub fn new() -> Self {
33 Self::default()
34 }
35
36 pub(crate) fn register(
38 &self,
39 filters: Arc<Mutex<Vec<String>>>,
40 sender: UnboundedSender<Message>,
41 ) {
42 self.subscriptions
43 .lock()
44 .expect("broker lock")
45 .push(Subscription { filters, sender });
46 }
47
48 pub(crate) fn publish(&self, message: &Message) {
51 let mut subscriptions = self.subscriptions.lock().expect("broker lock");
52 subscriptions.retain(|subscription| {
53 if subscription.sender.is_closed() {
54 return false;
55 }
56 let matched = subscription
57 .filters
58 .lock()
59 .expect("filters lock")
60 .iter()
61 .any(|filter| topic_matches(filter, &message.topic));
62 if matched {
63 let _ = subscription.sender.send(message.clone());
64 }
65 true
66 });
67 }
68}
69
70fn topic_matches(filter: &str, topic: &str) -> bool {
75 let mut filter_levels = filter.split('/');
76 let mut topic_levels = topic.split('/');
77 loop {
78 match (filter_levels.next(), topic_levels.next()) {
79 (Some("#"), _) => return true,
80 (Some("+"), Some(_)) => {}
81 (Some(filter_level), Some(topic_level)) if filter_level == topic_level => {}
82 (None, None) => return true,
83 _ => return false,
84 }
85 }
86}
87
88#[cfg(test)]
89mod tests {
90 use super::topic_matches;
91
92 #[test]
93 fn exact_topics_match() {
94 assert!(topic_matches("a/b/c", "a/b/c"));
95 assert!(!topic_matches("a/b/c", "a/b/d"));
96 assert!(!topic_matches("a/b", "a/b/c"));
97 assert!(!topic_matches("a/b/c", "a/b"));
98 }
99
100 #[test]
101 fn single_level_wildcard_matches_one_level() {
102 assert!(topic_matches("a/+/c", "a/b/c"));
103 assert!(topic_matches(
104 "sensors/+/temperature",
105 "sensors/1/temperature"
106 ));
107 assert!(!topic_matches("a/+/c", "a/b/c/d"));
108 assert!(!topic_matches("a/+", "a"));
109 }
110
111 #[test]
112 fn multi_level_wildcard_matches_the_rest() {
113 assert!(topic_matches("a/#", "a/b/c"));
114 assert!(topic_matches("a/#", "a"));
115 assert!(topic_matches("#", "a/b/c"));
116 assert!(!topic_matches("a/#", "b/c"));
117 }
118}