aws_iot_device_sdk/
defender.rs1use arrayvec::{ArrayString, ArrayVec};
2
3use self::Topic::*;
4use crate::common::*;
5
6const API_JSON_FORMAT: &str = "json";
7const API_CBOR_FORMAT: &str = "cbor";
8
9pub struct ThingDefender<'a> {
12 pub thing_name: &'a str,
13 pub api: Topic,
14}
15
16#[derive(Debug, PartialEq)]
27pub enum Topic {
28 JsonReportPublish,
29 JsonReportAccepted,
31 JsonReportRejected,
33 CborReportPublish,
35 CborReportAccepted,
37 CborReportRejected, }
40
41pub 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
81pub 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 [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 _ => 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}