aws_iot_device_sdk/
defender.rs

1use arrayvec::{ArrayString, ArrayVec};
2
3use self::Topic::*;
4use crate::common::*;
5
6const API_JSON_FORMAT: &str = "json";
7const API_CBOR_FORMAT: &str = "cbor";
8
9/// The struct outputs which API the topic is for. It also outputs
10/// the thing name in the given topic.
11pub struct ThingDefender<'a> {
12    pub thing_name: &'a str,
13    pub api: Topic,
14}
15
16///
17/// A Defender topic has the following format:"<Prefix><Thing Name><Bridge><Report Format><Suffix>"
18///
19/// Where:
20///     - Prefix = $aws/things/
21///     - Thing Name = Name of the thing.
22///     - Bridge = /defender/metrics/
23///     - Report Format = json or cbor
24///     - Suffix = /accepted or /rejected or empty
25///
26#[derive(Debug, PartialEq)]
27pub enum Topic {
28    JsonReportPublish,
29    /* Topic for publishing a JSON report. */
30    JsonReportAccepted,
31    /* Topic for getting a JSON report accepted response. */
32    JsonReportRejected,
33    /* Topic for getting a JSON report rejected response. */
34    CborReportPublish,
35    /* Topic for publishing a CBOR report. */
36    CborReportAccepted,
37    /* Topic for getting a CBOR report accepted response. */
38    CborReportRejected, /* Topic for getting a CBOR report rejected response. */
39}
40
41/// Populate the topic string for a Device Defender operation.
42///
43/// # Example
44/// ```
45/// use aws_iot_device_sdk::defender::Topic::*;
46/// use aws_iot_device_sdk::{defender};
47///
48/// let topic = defender::assemble_topic("chloe", defender::Topic::JsonReportPublish).unwrap();
49/// assert_eq!(&topic[..], "$aws/things/chloe/defender/metrics/json")
50/// ```
51pub fn assemble_topic(
52    thing_name: &str,
53    api: Topic,
54) -> Result<ArrayString<DEFENDER_TOPIC_MAX_LENGTH>, Error> {
55    is_valid_thing_name(thing_name)?;
56    let mut s = ArrayString::<DEFENDER_TOPIC_MAX_LENGTH>::new();
57    s.push_str(AWS_THINGS_PREFIX);
58    s.push_str(thing_name);
59    s.push_str(DEFENDER_API_BRIDGE);
60    s.push_str(op(&api));
61    s.push_str(suffix(&api));
62
63    Ok(s)
64}
65
66fn op(api: &Topic) -> &str {
67    match api {
68        JsonReportPublish | JsonReportAccepted | JsonReportRejected => API_JSON_FORMAT,
69        CborReportPublish | CborReportAccepted | CborReportRejected => API_CBOR_FORMAT,
70    }
71}
72
73fn suffix(topic_type: &Topic) -> &str {
74    match topic_type {
75        JsonReportAccepted | CborReportAccepted => SUFFIX_ACCEPTED,
76        JsonReportRejected | CborReportRejected => SUFFIX_REJECTED,
77        _ => "",
78    }
79}
80
81/// Check if the given topic is one of the Device Defender topics.
82///
83/// # Example
84/// ```
85/// use aws_iot_device_sdk::defender::Topic::*;
86/// use aws_iot_device_sdk::{defender};
87///
88/// let defender =
89///     defender::match_topic("$aws/things/chloe/defender/metrics/json/accepted").unwrap();
90///
91/// assert_eq!(defender.thing_name, "chloe");
92/// assert_eq!(defender.api, defender::Topic::JsonReportAccepted)
93/// ```
94pub fn match_topic(topic: &str) -> Result<ThingDefender, Error> {
95    is_valid_mqtt_topic(topic)?;
96
97    let s = is_valid_prefix(topic, AWS_THINGS_PREFIX)?;
98
99    let mid = s.find('/').ok_or(Error::FAIL);
100    let (thing_name, mut s) = s.split_at(mid?);
101    is_valid_thing_name(thing_name)?;
102
103    s = is_valid_bridge(s, DEFENDER_API_BRIDGE)?;
104
105    let v: ArrayVec<&str, 16> = s.split('/').collect();
106    let api: Topic;
107    match v[..] {
108        // ~$aws/things/<thingName>/defender/metrics/~<format>/suffix
109        [op, suffix] => {
110            match (op, suffix) {
111                (API_JSON_FORMAT, ACCEPTED) => api = JsonReportAccepted,
112                (API_JSON_FORMAT, REJECTED) => api = JsonReportRejected,
113                (API_CBOR_FORMAT, ACCEPTED) => api = CborReportAccepted,
114                (API_CBOR_FORMAT, REJECTED) => api = CborReportRejected,
115                _ => return Err(Error::NoMatch),
116            }
117            Ok(ThingDefender { thing_name, api })
118        }
119        // Not defender topic
120        _ => Err(Error::NoMatch),
121    }
122}
123#[cfg(test)]
124mod tests {
125    use crate::defender;
126    #[test]
127    fn assemble_topic_json() {
128        let topic = defender::assemble_topic("chloe", defender::Topic::JsonReportPublish).unwrap();
129        assert_eq!(&topic[..], "$aws/things/chloe/defender/metrics/json");
130    }
131
132    #[test]
133    fn assemble_topic_cbor_rejected() {
134        let topic = defender::assemble_topic("chloe", defender::Topic::CborReportRejected).unwrap();
135        assert_eq!(
136            &topic[..],
137            "$aws/things/chloe/defender/metrics/cbor/rejected"
138        );
139    }
140    #[test]
141    fn test_match_topic_some_name() {
142        let defender =
143            defender::match_topic("$aws/things/chloe/defender/metrics/json/accepted").unwrap();
144
145        assert_eq!(defender.thing_name, "chloe");
146        assert_eq!(defender.api, defender::Topic::JsonReportAccepted);
147    }
148}