aws_iot_device_sdk/
common.rs1use thiserror_no_std::Error;
2
3pub const MQTT_TOPIC_LENGTH_MAX: usize = 65535;
5
6pub const THINGNAME_MAX_LENGTH: usize = 128;
7pub const SHADOW_NAME_LENGTH_MAX: usize = 64;
8pub const JOBID_MAX_LENGTH: usize = 64;
9
10pub const TUNNEL_TOPIC_MAX_LENGTH: usize = THINGNAME_MAX_LENGTH + 32;
11pub const DEFENDER_TOPIC_MAX_LENGTH: usize = THINGNAME_MAX_LENGTH + 32;
12pub const JOBS_TOPIC_MAX_LENGTH: usize = THINGNAME_MAX_LENGTH + JOBID_MAX_LENGTH + 32;
13pub const SHADOW_TOPIC_MAX_LENGTH: usize = THINGNAME_MAX_LENGTH + SHADOW_NAME_LENGTH_MAX + 32;
14
15pub const AWS_THINGS_PREFIX: &str = "$aws/things/";
16
17pub const DEFENDER_API_BRIDGE: &str = "/defender/metrics/";
18pub const JOBS_API_BRIDGE: &str = "/jobs/";
19pub const SHADOW_API_BRIDGE: &str = "/shadow/";
20pub const NAMED_SHADOW_API_BRIDGE: &str = "/shadow/name/";
21pub const TUNNELS_API_BRIDGE: &str = "/tunnels/";
22
23pub const SUFFIX_ACCEPTED: &str = "/accepted";
24pub const SUFFIX_REJECTED: &str = "/rejected";
25
26pub const ACCEPTED: &str = "accepted";
27pub const REJECTED: &str = "rejected";
28
29#[derive(Error, Debug)]
30pub enum Error {
31 #[error("function encountered error.")]
32 FAIL,
33 #[error("Input mqtt topic is invalid.")]
34 MqttTopicFailed,
35 #[error("Could not parse the thing name.")]
36 ThingnameParseFailed,
37 #[error("Could not parse the type.")]
38 MessageTypeParseFailed,
39 #[error("Could not parse the root.")]
40 RootParseFailed,
41 #[error("Could not parse the shadow name (in the case of a named shadow topic).")]
42 ShadownameParseFailed,
43 #[error("Could not parse the job id.")]
44 JobsIdParseFailed,
45 #[error("The provided topic does not match any defender topic.")]
46 NoMatch,
47}
48
49fn is_valid_param(s: &str, max_len: usize) -> Result<(), Error> {
55 if !s.is_empty() && s.len() < max_len {
56 return Ok(());
57 }
58 Err(Error::FAIL)
59}
60
61pub fn is_valid_mqtt_topic(mqtt_topic: &str) -> Result<(), Error> {
67 is_valid_param(mqtt_topic, MQTT_TOPIC_LENGTH_MAX).map_err(|_| Error::MqttTopicFailed)
68}
69
70pub fn is_valid_prefix<'a>(s: &'a str, pre: &str) -> Result<&'a str, Error> {
76 s.strip_prefix(pre).ok_or(Error::NoMatch)
77}
78
79fn is_valid_name(name: &str, len: usize) -> Result<(), Error> {
85 is_valid_param(name, len)?;
86 for a in name.chars() {
87 match a {
88 '-' | '_' | '0'..='9' | 'A'..='Z' | 'a'..='z' | ':' => continue,
89 _ => return Err(Error::FAIL),
90 }
91 }
92 Ok(())
93}
94
95pub fn is_valid_thing_name(thing_name: &str) -> Result<(), Error> {
101 is_valid_name(thing_name, THINGNAME_MAX_LENGTH).map_err(|_| Error::ThingnameParseFailed)
102}
103
104pub fn is_valid_shadow_name(shadow_name: &str) -> Result<(), Error> {
110 is_valid_name(shadow_name, SHADOW_NAME_LENGTH_MAX).map_err(|_| Error::ShadownameParseFailed)
111}
112
113pub fn is_valid_bridge<'a>(s: &'a str, bridge: &str) -> Result<&'a str, Error> {
120 s.strip_prefix(bridge).ok_or(Error::RootParseFailed)
121}
122
123pub fn is_valid_job_id(job_id: &str) -> Result<(), Error> {
129 is_valid_param(job_id, JOBID_MAX_LENGTH)?;
131 for a in job_id.chars() {
132 match a {
133 '-' | '_' | '0'..='9' | 'A'..='Z' | 'a'..='z' => continue,
134 _ => return Err(Error::JobsIdParseFailed),
135 }
136 }
137 Ok(())
138}
139#[cfg(test)]
140mod tests {
141 use crate::common::*;
142 #[test]
143 fn valid_mqtt_topic() -> Result<(), Error> {
144 is_valid_mqtt_topic("hello/world")?;
145 Ok(())
146 }
147 #[test]
148 fn valid_prefix() -> Result<(), Error> {
149 is_valid_prefix("hello/world", "hello/")?;
150 Ok(())
151 }
152 #[test]
153 fn valid_thing_name() -> Result<(), Error> {
154 is_valid_thing_name("-_09AZaz:")?;
155 Ok(())
156 }
157 #[test]
158 fn valid_shadow_name() -> Result<(), Error> {
159 is_valid_shadow_name("common")?;
160 Ok(())
161 }
162 #[test]
163 fn valid_job_id() -> Result<(), Error> {
164 is_valid_job_id("_-09AZaz")?;
165 Ok(())
166 }
167}