aws_iot_device_sdk/
common.rs

1use thiserror_no_std::Error;
2
3// Limit imposed by the mqtt spec
4pub 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
49/// valid parameters?
50///
51/// # Example
52/// ```
53/// ```
54fn 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
61///
62/// valid mqtt topic?
63/// # Example
64/// ```
65/// ```
66pub 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
70///
71/// valid aws thing prefix?
72/// # Example
73/// ```
74/// ```
75pub 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
79///
80/// valid name in aws iot?
81/// # Example
82/// ```
83/// ```
84fn 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
95///
96/// valid aws iot thing name?
97/// # Example
98/// ```
99/// ```
100pub 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
104///
105/// valid aws iot shadow name?
106/// # Example
107/// ```
108/// ```
109pub 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
113///
114/// valid aws iot bridge?
115/// Like, "/shadow/" or "/jobs?", etc.
116/// # Example
117/// ```
118/// ```
119pub 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
123///
124/// valid aws iot job id?
125/// # Example
126/// ```
127/// ```
128pub fn is_valid_job_id(job_id: &str) -> Result<(), Error> {
129    // Thing thing_name cannot be empty or longer than JOBID_MAX_LENGTH
130    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}