diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 115 | ||||
-rw-r--r-- | src/mqtt.rs | 114 |
2 files changed, 116 insertions, 113 deletions
diff --git a/src/main.rs b/src/main.rs index b071620..c2a3797 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use rumqttc::{Event::Incoming, Packet, Publish, QoS}; use tokio::{io::AsyncWriteExt, process::Command, time::timeout}; mod config; +mod mqtt; const PROGRAM: &str = "mqttr"; @@ -41,40 +42,6 @@ async fn run(program: &[String], message: &Publish) -> anyhow::Result<ExitStatus Ok(result) } -fn topic_match(filter: &str, topic: &str) -> bool { - // TODO: Should probably just be a panic or prevented using types - if filter.is_empty() || topic.is_empty() { - return false; - } - if topic.starts_with('$') && (filter.starts_with('+') || filter.starts_with('#')) { - return false; - } - - // zip_longest would be nice - let mut topic = topic.split('/'); - let mut filter = filter.split('/'); - loop { - let topic_level = topic.next(); - return match filter.next() { - Some("#") => filter.next().is_none(), - Some("+") => { - if topic_level.is_none() { - false - } else { - continue; - } - } - Some(filter_level) => match topic_level { - Some(topic_level) if topic_level == filter_level => { - continue; - } - _ => false, - }, - None => topic_level.is_none(), - }; - } -} - #[tokio::main(flavor = "current_thread")] async fn main() -> anyhow::Result<()> { let mut conf_path: PathBuf = option_env!("SYSCONFDIR").unwrap_or("/usr/local/etc").into(); @@ -109,7 +76,7 @@ async fn main() -> anyhow::Result<()> { debug!("Received message: {p:?}"); let p = Rc::new(p); for (topic, route) in conf.routes.iter() { - if !topic_match(topic, &p.topic) { + if !mqtt::topic_match(topic, &p.topic) { continue; } debug!("Message {p:?} matched topic {topic}"); @@ -149,81 +116,3 @@ async fn main() -> anyhow::Result<()> { }) .await } - -#[cfg(test)] -mod tests { - use super::topic_match; - - #[test] - fn topic_match_basic() { - assert!(topic_match("foo/bar/baz", "foo/bar/baz")); - assert!(!topic_match("foo/bar/baz", "foo/bar/qux")); - assert!(!topic_match("foo/bar", "foo/bar/baz")); - assert!(!topic_match("foo/bar/baz", "foo/bar")); - } - - #[test] - fn topic_match_wildcard_hash() { - assert!(topic_match("foo/bar/baz/#", "foo/bar/baz")); - assert!(topic_match("foo/bar/baz/#", "foo/bar/baz/qux")); - assert!(topic_match("foo/bar/baz/#", "foo/bar/baz/qux/quux")); - assert!(topic_match("#", "foo/bar/baz")); - assert!(topic_match("#", "foo")); - assert!(topic_match("#", "/")); - assert!(topic_match("#", "/foo")); - assert!(!topic_match("foo/bar/#", "foo/baz/bar")); - assert!(!topic_match("foo/bar/#", "foo")); - } - - #[test] - fn topic_match_wildcard_plus() { - assert!(topic_match("foo/bar/+", "foo/bar/baz")); - assert!(topic_match("foo/bar/+", "foo/bar/qux")); - assert!(!topic_match("foo/bar/+", "foo/bar/baz/qux")); - assert!(topic_match("foo/+", "foo/")); - assert!(!topic_match("foo/+", "foo")); - assert!(topic_match("+", "foo")); - assert!(topic_match("+/bar/#", "foo/bar/baz/qux")); - assert!(topic_match("+/bar/#", "qux/bar")); - assert!(topic_match("foo/+/baz", "foo/bar/baz")); - assert!(topic_match("foo/+/baz", "foo/qux/baz")); - assert!(!topic_match("foo/+/baz", "foo/bar/qux")); - assert!(topic_match("+/+", "/foo")); - assert!(topic_match("/+", "/foo")); - assert!(!topic_match("+", "/foo")); - } - - #[test] - fn topic_match_dollar() { - assert!(!topic_match("#", "$foo/bar")); - assert!(!topic_match("+/bar/baz", "$foo/bar/baz")); - assert!(topic_match("$foo/#", "$foo/bar")); - assert!(topic_match("$foo/#", "$foo/bar/baz")); - assert!(topic_match("$foo/#", "$foo")); - assert!(topic_match("$foo/bar/+", "$foo/bar/baz")); - assert!(!topic_match("$foo/#", "foo/bar")); - } - - #[test] - fn topic_match_edge_cases() { - assert!(!topic_match("foo", "FOO")); - assert!(topic_match("foo bar", "foo bar")); - assert!(!topic_match("foo bar", "foo bar")); - assert!(!topic_match("foo bar", "foo bar")); - assert!(!topic_match("/foo", "foo")); - assert!(!topic_match("foo", "/foo")); - assert!(topic_match("foo//bar", "foo//bar")); - assert!(!topic_match("foo/bar", "foo//bar")); - assert!(!topic_match("foo//bar", "foo/bar")); - assert!(!topic_match("foo//baz", "foo/bar/baz")); - assert!(!topic_match("foo/bar/baz", "foo//baz")); - assert!(topic_match("foo/+/baz", "foo//baz")); - assert!(topic_match("/", "/")); - assert!(!topic_match("/", "foo")); - assert!(!topic_match("foo", "/")); - assert!(!topic_match("", "")); - assert!(!topic_match("+", "")); - assert!(!topic_match("#", "")); - assert!(!topic_match("foo/#/baz", "foo/bar/baz")); - } -} diff --git a/src/mqtt.rs b/src/mqtt.rs new file mode 100644 index 0000000..f12d51c --- /dev/null +++ b/src/mqtt.rs @@ -0,0 +1,114 @@ +// SPDX-FileCopyrightText: 2025 Tomasz Kramkowski <tomasz@kramkow.ski> +// SPDX-License-Identifier: GPL-3.0-or-later + +pub fn topic_match(filter: &str, topic: &str) -> bool { + // TODO: Should probably just be a panic or prevented using types + if filter.is_empty() || topic.is_empty() { + return false; + } + if topic.starts_with('$') && (filter.starts_with('+') || filter.starts_with('#')) { + return false; + } + + // zip_longest would be nice + let mut topic = topic.split('/'); + let mut filter = filter.split('/'); + loop { + let topic_level = topic.next(); + return match filter.next() { + Some("#") => filter.next().is_none(), + Some("+") => { + if topic_level.is_none() { + false + } else { + continue; + } + } + Some(filter_level) => match topic_level { + Some(topic_level) if topic_level == filter_level => { + continue; + } + _ => false, + }, + None => topic_level.is_none(), + }; + } +} + +#[cfg(test)] +mod tests { + use super::topic_match; + + #[test] + fn topic_match_basic() { + assert!(topic_match("foo/bar/baz", "foo/bar/baz")); + assert!(!topic_match("foo/bar/baz", "foo/bar/qux")); + assert!(!topic_match("foo/bar", "foo/bar/baz")); + assert!(!topic_match("foo/bar/baz", "foo/bar")); + } + + #[test] + fn topic_match_wildcard_hash() { + assert!(topic_match("foo/bar/baz/#", "foo/bar/baz")); + assert!(topic_match("foo/bar/baz/#", "foo/bar/baz/qux")); + assert!(topic_match("foo/bar/baz/#", "foo/bar/baz/qux/quux")); + assert!(topic_match("#", "foo/bar/baz")); + assert!(topic_match("#", "foo")); + assert!(topic_match("#", "/")); + assert!(topic_match("#", "/foo")); + assert!(!topic_match("foo/bar/#", "foo/baz/bar")); + assert!(!topic_match("foo/bar/#", "foo")); + } + + #[test] + fn topic_match_wildcard_plus() { + assert!(topic_match("foo/bar/+", "foo/bar/baz")); + assert!(topic_match("foo/bar/+", "foo/bar/qux")); + assert!(!topic_match("foo/bar/+", "foo/bar/baz/qux")); + assert!(topic_match("foo/+", "foo/")); + assert!(!topic_match("foo/+", "foo")); + assert!(topic_match("+", "foo")); + assert!(topic_match("+/bar/#", "foo/bar/baz/qux")); + assert!(topic_match("+/bar/#", "qux/bar")); + assert!(topic_match("foo/+/baz", "foo/bar/baz")); + assert!(topic_match("foo/+/baz", "foo/qux/baz")); + assert!(!topic_match("foo/+/baz", "foo/bar/qux")); + assert!(topic_match("+/+", "/foo")); + assert!(topic_match("/+", "/foo")); + assert!(!topic_match("+", "/foo")); + } + + #[test] + fn topic_match_dollar() { + assert!(!topic_match("#", "$foo/bar")); + assert!(!topic_match("+/bar/baz", "$foo/bar/baz")); + assert!(topic_match("$foo/#", "$foo/bar")); + assert!(topic_match("$foo/#", "$foo/bar/baz")); + assert!(topic_match("$foo/#", "$foo")); + assert!(topic_match("$foo/bar/+", "$foo/bar/baz")); + assert!(!topic_match("$foo/#", "foo/bar")); + } + + #[test] + fn topic_match_edge_cases() { + assert!(!topic_match("foo", "FOO")); + assert!(topic_match("foo bar", "foo bar")); + assert!(!topic_match("foo bar", "foo bar")); + assert!(!topic_match("foo bar", "foo bar")); + assert!(!topic_match("/foo", "foo")); + assert!(!topic_match("foo", "/foo")); + assert!(topic_match("foo//bar", "foo//bar")); + assert!(!topic_match("foo/bar", "foo//bar")); + assert!(!topic_match("foo//bar", "foo/bar")); + assert!(!topic_match("foo//baz", "foo/bar/baz")); + assert!(!topic_match("foo/bar/baz", "foo//baz")); + assert!(topic_match("foo/+/baz", "foo//baz")); + assert!(topic_match("/", "/")); + assert!(!topic_match("/", "foo")); + assert!(!topic_match("foo", "/")); + assert!(!topic_match("", "")); + assert!(!topic_match("+", "")); + assert!(!topic_match("#", "")); + assert!(!topic_match("foo/#/baz", "foo/bar/baz")); + } +} |