aws_iot_device_sdk_embedded_rust/
common.rs

1// Limit imposed by the mqtt spec
2pub const MQTT_TOPIC_LENGTH_MAX: usize = 65535;
3
4pub const THINGNAME_MAX_LENGTH: usize = 128;
5pub const SHADOW_NAME_LENGTH_MAX: usize = 64;
6pub const JOBID_MAX_LENGTH: usize = 64;
7
8pub const TUNNEL_TOPIC_MAX_LENGTH: usize = THINGNAME_MAX_LENGTH + 32;
9pub const DEFENDER_TOPIC_MAX_LENGTH: usize = THINGNAME_MAX_LENGTH + 32;
10pub const JOBS_TOPIC_MAX_LENGTH: usize = THINGNAME_MAX_LENGTH + JOBID_MAX_LENGTH + 32;
11pub const SHADOW_TOPIC_MAX_LENGTH: usize = THINGNAME_MAX_LENGTH + SHADOW_NAME_LENGTH_MAX + 32;
12
13pub const AWS_THINGS_PREFIX: &str = "$aws/things/";
14
15pub const SUFFIX_ACCEPTED: &str = "/accepted";
16pub const SUFFIX_REJECTED: &str = "/rejected";
17
18pub const ACCEPTED: &str = "accepted";
19pub const REJECTED: &str = "rejected";
20
21#[derive(Debug, PartialEq, Clone)]
22pub enum Error {
23    FAIL,                   /* function encountered error. */
24    MqttTopicFailed,        /* Input mqtt topic is invalid. */
25    ThingnameParseFailed,   /* Could not parse the thing name. */
26    MessageTypeParseFailed, /* Could not parse the type. */
27    RootParseFailed,        /* Could not parse the root. */
28    ShadownameParseFailed, /* Could not parse the shadow name (in the case of a named shadow topic). */
29    JobsIdParseFailed,     /* Could not parse the job id. */
30    NoMatch,               /* The provided topic does not match any defender topic. */
31}
32
33/// valid parameters?
34///
35/// # Example
36/// ```
37/// ```
38fn is_valid_param(s: &str, max_len: usize) -> Result<(), Error> {
39    if !s.is_empty() && s.len() < max_len {
40        return Ok(());
41    }
42    Err(Error::FAIL)
43}
44
45///
46/// valid mqtt topic?
47/// # Example
48/// ```
49/// ```
50pub fn is_valid_mqtt_topic(mqtt_topic: &str) -> Result<(), Error> {
51    is_valid_param(mqtt_topic, MQTT_TOPIC_LENGTH_MAX).map_err(|_| Error::MqttTopicFailed)
52}
53
54///
55/// valid aws thing prefix?
56/// # Example
57/// ```
58/// ```
59pub fn is_valid_prefix<'a>(s: &'a str, pre: &str) -> Result<&'a str, Error> {
60    s.strip_prefix(pre).ok_or(Error::NoMatch)
61}
62
63///
64/// valid name in aws iot?
65/// # Example
66/// ```
67/// ```
68fn is_valid_name(name: &str, len: usize) -> Result<(), Error> {
69    is_valid_param(name, len)?;
70    for a in name.chars() {
71        match a {
72            '-' | '_' | '0'..='9' | 'A'..='Z' | 'a'..='z' | ':' => continue,
73            _ => return Err(Error::FAIL),
74        }
75    }
76    Ok(())
77}
78
79///
80/// valid aws iot thing name?
81/// # Example
82/// ```
83/// ```
84pub fn is_valid_thing_name(thing_name: &str) -> Result<(), Error> {
85    is_valid_name(thing_name, THINGNAME_MAX_LENGTH).map_err(|_| Error::ThingnameParseFailed)
86}
87
88///
89/// valid aws iot shadow name?
90/// # Example
91/// ```
92/// ```
93pub fn is_valid_shadow_name(shadow_name: &str) -> Result<(), Error> {
94    is_valid_name(shadow_name, SHADOW_NAME_LENGTH_MAX).map_err(|_| Error::ShadownameParseFailed)
95}
96
97///
98/// valid aws iot bridge?
99/// Like, "/shadow/" or "/jobs?", etc.
100/// # Example
101/// ```
102/// ```
103pub fn is_valid_bridge<'a>(s: &'a str, bridge: &str) -> Result<&'a str, Error> {
104    s.strip_prefix(bridge).ok_or(Error::RootParseFailed)
105}
106
107///
108/// valid aws iot job id?
109/// # Example
110/// ```
111/// ```
112pub fn is_valid_job_id(job_id: &str) -> Result<(), Error> {
113    // Thing thing_name cannot be empty or longer than JOBID_MAX_LENGTH
114    is_valid_param(job_id, JOBID_MAX_LENGTH)?;
115    for a in job_id.chars() {
116        match a {
117            '-' | '_' | '0'..='9' | 'A'..='Z' | 'a'..='z' => continue,
118            _ => return Err(Error::JobsIdParseFailed),
119        }
120    }
121    Ok(())
122}
123#[cfg(test)]
124mod tests {
125    use crate::common::*;
126    #[test]
127    fn valid_mqtt_topic() -> Result<(), Error> {
128        is_valid_mqtt_topic("hello/world")?;
129        Ok(())
130    }
131    #[test]
132    fn valid_prefix() -> Result<(), Error> {
133        is_valid_prefix("hello/world", "hello/")?;
134        Ok(())
135    }
136    #[test]
137    fn valid_thing_name() -> Result<(), Error> {
138        is_valid_thing_name("-_09AZaz:")?;
139        Ok(())
140    }
141    #[test]
142    fn valid_shadow_name() -> Result<(), Error> {
143        is_valid_shadow_name("common")?;
144        Ok(())
145    }
146    #[test]
147    fn valid_job_id() -> Result<(), Error> {
148        is_valid_job_id("_-09AZaz")?;
149        Ok(())
150    }
151}