tether_agent/channels/
definitions.rs

1use log::{debug, error, warn};
2use serde::{Deserialize, Serialize};
3
4use super::tether_compliant_topic::TetherOrCustomTopic;
5
6pub trait ChannelDefinitionCommon<'a> {
7    fn name(&'a self) -> &'a str;
8    /// Return the generated topic string actually used by the Channel
9    fn generated_topic(&'a self) -> &'a str;
10    /// Return the custom or Tether-compliant topic
11    fn topic(&'a self) -> &'a TetherOrCustomTopic;
12    fn qos(&'a self) -> i32;
13}
14
15#[derive(Serialize, Deserialize, Debug)]
16pub struct ChannelReceiverDefinition {
17    name: String,
18    topic: TetherOrCustomTopic,
19    qos: i32,
20}
21
22impl ChannelDefinitionCommon<'_> for ChannelReceiverDefinition {
23    fn name(&self) -> &str {
24        &self.name
25    }
26
27    fn generated_topic(&self) -> &str {
28        match &self.topic {
29            TetherOrCustomTopic::Custom(s) => {
30                debug!(
31                    "Channel named \"{}\" has custom topic \"{}\"",
32                    &self.name, &s
33                );
34                s
35            }
36            TetherOrCustomTopic::Tether(t) => {
37                debug!(
38                    "Channel named \"{}\" has Tether-compliant topic \"{:?}\"",
39                    &self.name, t
40                );
41                t.topic()
42            }
43        }
44    }
45
46    fn topic(&'_ self) -> &'_ TetherOrCustomTopic {
47        &self.topic
48    }
49
50    fn qos(&self) -> i32 {
51        self.qos
52    }
53}
54
55impl ChannelReceiverDefinition {
56    pub fn new(
57        name: &str,
58        topic: TetherOrCustomTopic,
59        qos: Option<i32>,
60    ) -> ChannelReceiverDefinition {
61        ChannelReceiverDefinition {
62            name: String::from(name),
63            topic,
64            qos: qos.unwrap_or(1),
65        }
66    }
67
68    /// Use the topic of an incoming message to check against the definition of an Channel Receiver.
69    ///
70    /// Due to the use of wildcard subscriptions, multiple topic strings might match a given
71    /// Channel Receiver definition. e.g. `someRole/channelMessages` and `anotherRole/channelMessages` and `someRole/channelMessages/specificID`
72    /// should ALL match on an Channel Receiver named `channelMessages` unless more specific Role and/or ID
73    /// parts were specified in the Channel Receiver Definition.
74    ///
75    /// In the case where an Channel Receiver was defined with a completely manually-specified topic string,
76    /// this function returns a warning and marks ANY incoming message as a valid match; the end-user
77    /// developer is expected to match against topic strings themselves.
78    pub fn matches(&self, incoming_topic: &TetherOrCustomTopic) -> bool {
79        match incoming_topic {
80            TetherOrCustomTopic::Tether(incoming_three_parts) => match &self.topic {
81                TetherOrCustomTopic::Tether(my_tpt) => {
82                    let matches_role =
83                        my_tpt.role() == "+" || my_tpt.role().eq(incoming_three_parts.role());
84                    let matches_channel_name = my_tpt.channel_name() == "+"
85                        || my_tpt
86                            .channel_name()
87                            .eq(incoming_three_parts.channel_name());
88                    let matches_id = match my_tpt.id() {
89                        Some(specified_id) => match incoming_three_parts.id() {
90                            Some(incoming_id) => specified_id == incoming_id,
91                            None => false,
92                        },
93                        None => true,
94                    };
95
96                    debug!("Test match for Channel named \"{}\" with def {:?} against {:?} => matches_role? {}, matches_id? {}, matches_channel_name? {}", &self.name, &self.topic, &incoming_three_parts, matches_role, matches_id, matches_channel_name);
97                    matches_role && matches_id && matches_channel_name
98                }
99                TetherOrCustomTopic::Custom(my_custom_topic) => {
100                    debug!(
101                    "Custom/manual topic \"{}\" on Channel \"{}\" cannot be matched automatically; please filter manually for this",
102                    &my_custom_topic,
103                    self.name()
104                );
105                    my_custom_topic.as_str() == "#"
106                        || my_custom_topic.as_str() == incoming_three_parts.topic()
107                }
108            },
109            TetherOrCustomTopic::Custom(incoming_custom) => match &self.topic {
110                TetherOrCustomTopic::Custom(my_custom_topic) => {
111                    if my_custom_topic.as_str() == "#"
112                        || my_custom_topic.as_str() == incoming_custom.as_str()
113                    {
114                        true
115                    } else {
116                        warn!(
117                            "Incoming topic \"{}\" is not a Tether-Compliant topic",
118                            &incoming_custom
119                        );
120                        false
121                    }
122                }
123                TetherOrCustomTopic::Tether(_) => {
124                    error!("Incoming is NOT Tether Compliant Topic but this Channel DOES have Tether Compliant Topic; cannot decide match");
125                    false
126                }
127            },
128        }
129    }
130}
131
132#[derive(Serialize, Deserialize, Debug)]
133pub struct ChannelSenderDefinition {
134    name: String,
135    topic: TetherOrCustomTopic,
136    qos: i32,
137    retain: bool,
138}
139
140impl ChannelDefinitionCommon<'_> for ChannelSenderDefinition {
141    fn name(&'_ self) -> &'_ str {
142        &self.name
143    }
144
145    fn generated_topic(&self) -> &str {
146        match &self.topic {
147            TetherOrCustomTopic::Custom(s) => s,
148            TetherOrCustomTopic::Tether(t) => t.topic(),
149        }
150    }
151
152    fn topic(&'_ self) -> &'_ TetherOrCustomTopic {
153        &self.topic
154    }
155
156    fn qos(&'_ self) -> i32 {
157        self.qos
158    }
159}
160
161impl ChannelSenderDefinition {
162    pub fn new(
163        name: &str,
164        topic: TetherOrCustomTopic,
165        qos: Option<i32>,
166        retain: Option<bool>,
167    ) -> ChannelSenderDefinition {
168        ChannelSenderDefinition {
169            name: String::from(name),
170            topic,
171            qos: qos.unwrap_or(1),
172            retain: retain.unwrap_or(false),
173        }
174    }
175
176    pub fn retain(&self) -> bool {
177        self.retain
178    }
179}
180
181#[derive(Serialize, Deserialize, Debug)]
182pub enum ChannelDefinition {
183    ChannelReceiver(ChannelReceiverDefinition),
184    ChannelSender(ChannelSenderDefinition),
185}
186
187impl ChannelDefinition {
188    pub fn name(&self) -> &str {
189        match self {
190            ChannelDefinition::ChannelReceiver(p) => p.name(),
191            ChannelDefinition::ChannelSender(p) => p.name(),
192        }
193    }
194
195    pub fn generated_topic(&self) -> &str {
196        match self {
197            ChannelDefinition::ChannelReceiver(p) => p.generated_topic(),
198            ChannelDefinition::ChannelSender(p) => p.generated_topic(),
199        }
200    }
201
202    pub fn matches(&self, topic: &TetherOrCustomTopic) -> bool {
203        match self {
204            ChannelDefinition::ChannelReceiver(p) => p.matches(topic),
205            ChannelDefinition::ChannelSender(_) => {
206                error!("We don't check matches for Channel Senders");
207                false
208            }
209        }
210    }
211}
212
213#[cfg(test)]
214mod tests {
215
216    use crate::{
217        tether_compliant_topic::{parse_channel_name, TetherCompliantTopic, TetherOrCustomTopic},
218        ChannelDefinitionCommon, ChannelReceiverDefinition,
219    };
220
221    #[test]
222    fn receiver_match_tpt() {
223        let channel_def = ChannelReceiverDefinition::new(
224            "testChannel",
225            TetherOrCustomTopic::Tether(TetherCompliantTopic::new_for_subscribe(
226                "testChannel",
227                None,
228                None,
229            )),
230            None,
231        );
232
233        assert_eq!(&channel_def.name, "testChannel");
234        assert_eq!(channel_def.generated_topic(), "+/testChannel/#");
235        assert_eq!(
236            parse_channel_name("someRole/testChannel"),
237            Some("testChannel")
238        );
239        assert_eq!(
240            parse_channel_name("someRole/testChannel/something"),
241            Some("testChannel")
242        );
243        assert!(channel_def.matches(&TetherOrCustomTopic::Tether(
244            TetherCompliantTopic::new_three("dummy", "testChannel", "#")
245        )));
246        assert!(!channel_def.matches(&TetherOrCustomTopic::Tether(
247            TetherCompliantTopic::new_three("dummy", "anotherChannel", "#")
248        )));
249    }
250
251    #[test]
252    fn receiver_match_tpt_custom_role() {
253        let channel_def = ChannelReceiverDefinition::new(
254            "customChanel",
255            TetherOrCustomTopic::Tether(TetherCompliantTopic::new_for_subscribe(
256                "customChanel",
257                Some("customRole"),
258                None,
259            )),
260            None,
261        );
262
263        assert_eq!(&channel_def.name, "customChanel");
264        assert_eq!(channel_def.generated_topic(), "customRole/customChanel/#");
265        assert!(channel_def.matches(&TetherOrCustomTopic::Tether(
266            TetherCompliantTopic::new_three("customRole", "customChanel", "#")
267        )));
268        assert!(channel_def.matches(&TetherOrCustomTopic::Tether(
269            TetherCompliantTopic::new_three("customRole", "customChanel", "andAnythingElse")
270        )));
271        assert!(!channel_def.matches(&TetherOrCustomTopic::Tether(
272            TetherCompliantTopic::new_three("customRole", "notMyChannel", "#")
273        ))); // wrong incoming Channel Name
274        assert!(!channel_def.matches(&TetherOrCustomTopic::Tether(
275            TetherCompliantTopic::new_three("someOtherRole", "customChanel", "#")
276        ))); // wrong incoming Role
277    }
278
279    #[test]
280    fn receiver_match_custom_id() {
281        let channel_def = ChannelReceiverDefinition::new(
282            "customChanel",
283            TetherOrCustomTopic::Tether(TetherCompliantTopic::new_for_subscribe(
284                "customChanel",
285                None,
286                Some("specificID"),
287            )),
288            None,
289        );
290
291        assert_eq!(&channel_def.name, "customChanel");
292        assert_eq!(channel_def.generated_topic(), "+/customChanel/specificID");
293        assert!(channel_def.matches(&TetherOrCustomTopic::Tether(
294            TetherCompliantTopic::new_three("anyRole", "customChanel", "specificID",)
295        )));
296        assert!(channel_def.matches(&TetherOrCustomTopic::Tether(
297            TetherCompliantTopic::new_three("anotherRole", "customChanel", "specificID",)
298        ))); // wrong incoming Role
299        assert!(!channel_def.matches(&TetherOrCustomTopic::Tether(
300            TetherCompliantTopic::new_three("anyRole", "notMyChannel", "specificID",)
301        ))); // wrong incoming Channel Name
302        assert!(!channel_def.matches(&TetherOrCustomTopic::Tether(
303            TetherCompliantTopic::new_three("anyRole", "customChanel", "anotherID",)
304        ))); // wrong incoming ID
305    }
306
307    #[test]
308    fn receiver_match_both() {
309        let channel_def = ChannelReceiverDefinition::new(
310            "customChanel",
311            TetherOrCustomTopic::Tether(TetherCompliantTopic::new_for_subscribe(
312                "customChanel",
313                Some("specificRole"),
314                Some("specificID"),
315            )),
316            None,
317        );
318
319        assert_eq!(&channel_def.name, "customChanel");
320        assert_eq!(
321            channel_def.generated_topic(),
322            "specificRole/customChanel/specificID"
323        );
324        assert!(channel_def.matches(&TetherOrCustomTopic::Tether(
325            TetherCompliantTopic::new_three("specificRole", "customChanel", "specificID",)
326        )));
327        assert!(!channel_def.matches(&TetherOrCustomTopic::Custom(
328            "specificRole/notMyChannel/specificID".into()
329        ))); // wrong incoming Channel Name
330        assert!(!channel_def.matches(&TetherOrCustomTopic::Custom(
331            "specificRole/customChanel/anotherID".into()
332        ))); // wrong incoming ID
333        assert!(!channel_def.matches(&TetherOrCustomTopic::Custom(
334            "anotherRole/customChanel/anotherID".into()
335        ))); // wrong incoming Role
336    }
337
338    #[test]
339    fn receiver_match_custom_topic() {
340        let channel_def = ChannelReceiverDefinition::new(
341            "customChanel",
342            TetherOrCustomTopic::Custom("one/two/three/four/five".into()), // not a standard Tether Three Part Topic
343            None,
344        );
345
346        assert_eq!(channel_def.name(), "customChanel");
347        // it will match on exactly the same topic:
348        assert!(channel_def.matches(&TetherOrCustomTopic::Custom(
349            "one/two/three/four/five".into()
350        )));
351
352        // it will NOT match on anything else:
353        assert!(!channel_def.matches(&TetherOrCustomTopic::Custom("one/one/one/one/one".into())));
354    }
355
356    #[test]
357    fn receiver_match_wildcard() {
358        let channel_def = ChannelReceiverDefinition::new(
359            "everything",
360            TetherOrCustomTopic::Custom("#".into()), // fully legal, but not a standard Three Part Topic
361            None,
362        );
363
364        assert_eq!(channel_def.name(), "everything");
365
366        // Standard TPT will match
367        assert!(channel_def.matches(&TetherOrCustomTopic::Tether(
368            TetherCompliantTopic::new_three("any", "chanelName", "#")
369        )));
370
371        // Anything will match, even custom incoming
372        assert!(channel_def.matches(&TetherOrCustomTopic::Custom(
373            "one/two/three/four/five".into()
374        )));
375    }
376}