1use core::fmt::Display;
2use core::fmt::Write;
3use core::str::FromStr;
4
5use heapless::String;
6use mqttrust::{Mqtt, QoS, SubscribeTopic};
7
8use super::Error;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11#[cfg_attr(feature = "defmt", derive(defmt::Format))]
12pub enum Direction {
13 Incoming,
14 Outgoing,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq)]
18#[cfg_attr(feature = "defmt", derive(defmt::Format))]
19pub enum PayloadFormat {
20 #[cfg(feature = "provision_cbor")]
21 Cbor,
22 Json,
23}
24
25impl Display for PayloadFormat {
26 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
27 match self {
28 #[cfg(feature = "provision_cbor")]
29 Self::Cbor => write!(f, "cbor"),
30 Self::Json => write!(f, "json"),
31 }
32 }
33}
34
35impl FromStr for PayloadFormat {
36 type Err = ();
37
38 fn from_str(s: &str) -> Result<Self, Self::Err> {
39 match s {
40 #[cfg(feature = "provision_cbor")]
41 "cbor" => Ok(Self::Cbor),
42 "json" => Ok(Self::Json),
43 _ => Err(()),
44 }
45 }
46}
47
48#[derive(Debug, Clone, PartialEq)]
49#[cfg_attr(feature = "defmt", derive(defmt::Format))]
50pub enum Topic<'a> {
51 RegisterThing(&'a str, PayloadFormat),
54
55 CreateKeysAndCertificate(PayloadFormat),
57
58 CreateCertificateFromCsr(PayloadFormat),
60
61 RegisterThingAccepted(&'a str, PayloadFormat),
64
65 RegisterThingRejected(&'a str, PayloadFormat),
67
68 CreateKeysAndCertificateAccepted(PayloadFormat),
70
71 CreateKeysAndCertificateRejected(PayloadFormat),
73
74 CreateCertificateFromCsrAccepted(PayloadFormat),
76
77 CreateCertificateFromCsrRejected(PayloadFormat),
79}
80
81impl<'a> Topic<'a> {
82 const CERT_PREFIX: &'static str = "$aws/certificates";
83 const PROVISIONING_PREFIX: &'static str = "$aws/provisioning-templates";
84
85 pub fn check(s: &'a str) -> bool {
86 s.starts_with(Self::CERT_PREFIX) || s.starts_with(Self::PROVISIONING_PREFIX)
87 }
88
89 pub fn from_str(s: &'a str) -> Option<Self> {
90 let tt = s.splitn(6, '/').collect::<heapless::Vec<&str, 6>>();
91 match (tt.get(0), tt.get(1)) {
92 (Some(&"$aws"), Some(&"provisioning-templates")) => {
93 match (tt.get(2), tt.get(3), tt.get(4), tt.get(5)) {
96 (
97 Some(template_name),
98 Some(&"provision"),
99 Some(payload_format),
100 Some(&"accepted"),
101 ) => Some(Topic::RegisterThingAccepted(
102 *template_name,
103 PayloadFormat::from_str(payload_format).ok()?,
104 )),
105 (
106 Some(template_name),
107 Some(&"provision"),
108 Some(payload_format),
109 Some(&"rejected"),
110 ) => Some(Topic::RegisterThingRejected(
111 *template_name,
112 PayloadFormat::from_str(payload_format).ok()?,
113 )),
114 _ => None,
115 }
116 }
117 (Some(&"$aws"), Some(&"certificates")) => {
118 match (tt.get(2), tt.get(3), tt.get(4)) {
121 (Some(&"create"), Some(payload_format), Some(&"accepted")) => {
122 Some(Topic::CreateKeysAndCertificateAccepted(
123 PayloadFormat::from_str(payload_format).ok()?,
124 ))
125 }
126 (Some(&"create"), Some(payload_format), Some(&"rejected")) => {
127 Some(Topic::CreateKeysAndCertificateRejected(
128 PayloadFormat::from_str(payload_format).ok()?,
129 ))
130 }
131 (Some(&"create-from-csr"), Some(payload_format), Some(&"accepted")) => {
132 Some(Topic::CreateCertificateFromCsrAccepted(
133 PayloadFormat::from_str(payload_format).ok()?,
134 ))
135 }
136 (Some(&"create-from-csr"), Some(payload_format), Some(&"rejected")) => {
137 Some(Topic::CreateCertificateFromCsrRejected(
138 PayloadFormat::from_str(payload_format).ok()?,
139 ))
140 }
141 _ => None,
142 }
143 }
144 _ => None,
145 }
146 }
147
148 pub fn direction(&self) -> Direction {
149 if matches!(
150 self,
151 Topic::RegisterThing(_, _)
152 | Topic::CreateKeysAndCertificate(_)
153 | Topic::CreateCertificateFromCsr(_)
154 ) {
155 Direction::Outgoing
156 } else {
157 Direction::Incoming
158 }
159 }
160
161 pub fn format<const L: usize>(&self) -> Result<String<L>, Error> {
162 let mut topic_path = String::new();
163 match self {
164 Self::RegisterThing(template_name, payload_format) => {
165 topic_path.write_fmt(format_args!(
166 "{}/{}/provision/{}",
167 Self::PROVISIONING_PREFIX,
168 template_name,
169 payload_format,
170 ))
171 }
172 Topic::RegisterThingAccepted(template_name, payload_format) => {
173 topic_path.write_fmt(format_args!(
174 "{}/{}/provision/{}/accepted",
175 Self::PROVISIONING_PREFIX,
176 template_name,
177 payload_format,
178 ))
179 }
180 Topic::RegisterThingRejected(template_name, payload_format) => {
181 topic_path.write_fmt(format_args!(
182 "{}/{}/provision/{}/rejected",
183 Self::PROVISIONING_PREFIX,
184 template_name,
185 payload_format,
186 ))
187 }
188
189 Topic::CreateKeysAndCertificate(payload_format) => topic_path.write_fmt(format_args!(
190 "{}/create/{}",
191 Self::CERT_PREFIX,
192 payload_format,
193 )),
194
195 Topic::CreateKeysAndCertificateAccepted(payload_format) => topic_path.write_fmt(
196 format_args!("{}/create/{}/accepted", Self::CERT_PREFIX, payload_format),
197 ),
198 Topic::CreateKeysAndCertificateRejected(payload_format) => topic_path.write_fmt(
199 format_args!("{}/create/{}/rejected", Self::CERT_PREFIX, payload_format),
200 ),
201
202 Topic::CreateCertificateFromCsr(payload_format) => topic_path.write_fmt(format_args!(
203 "{}/create-from-csr/{}",
204 Self::CERT_PREFIX,
205 payload_format,
206 )),
207 Topic::CreateCertificateFromCsrAccepted(payload_format) => topic_path.write_fmt(
208 format_args!("{}/create-from-csr/{}", Self::CERT_PREFIX, payload_format),
209 ),
210 Topic::CreateCertificateFromCsrRejected(payload_format) => topic_path.write_fmt(
211 format_args!("{}/create-from-csr/{}", Self::CERT_PREFIX, payload_format),
212 ),
213 }
214 .map_err(|_| Error::Overflow)?;
215
216 Ok(topic_path)
217 }
218}
219
220#[derive(Default)]
221pub struct Subscribe<'a, const N: usize> {
222 topics: heapless::Vec<(Topic<'a>, QoS), N>,
223}
224
225impl<'a, const N: usize> Subscribe<'a, N> {
226 pub fn new() -> Self {
227 Self::default()
228 }
229
230 pub fn topic(self, topic: Topic<'a>, qos: QoS) -> Self {
231 if topic.direction() != Direction::Incoming {
233 return self;
234 }
235
236 if self.topics.iter().any(|(t, _)| t == &topic) {
237 return self;
238 }
239
240 let mut topics = self.topics;
241 topics.push((topic, qos)).ok();
242
243 Self { topics }
244 }
245
246 pub fn topics(self) -> Result<heapless::Vec<(heapless::String<128>, QoS), N>, Error> {
247 self.topics
248 .iter()
249 .map(|(topic, qos)| Ok((topic.clone().format()?, *qos)))
250 .collect()
251 }
252
253 pub fn send<M: Mqtt>(self, mqtt: &M) -> Result<(), Error> {
254 if self.topics.is_empty() {
255 return Ok(());
256 }
257
258 let topic_paths = self.topics()?;
259
260 debug!("Subscribing! {:?}", topic_paths);
261
262 let topics: heapless::Vec<_, N> = topic_paths
263 .iter()
264 .map(|(s, qos)| SubscribeTopic {
265 topic_path: s.as_str(),
266 qos: *qos,
267 })
268 .collect();
269
270 for t in topics.chunks(5) {
271 mqtt.subscribe(t)?;
272 }
273 Ok(())
274 }
275}
276
277#[derive(Default)]
278pub struct Unsubscribe<'a, const N: usize> {
279 topics: heapless::Vec<Topic<'a>, N>,
280}
281
282impl<'a, const N: usize> Unsubscribe<'a, N> {
283 pub fn new() -> Self {
284 Self::default()
285 }
286
287 pub fn topic(self, topic: Topic<'a>) -> Self {
288 if topic.direction() != Direction::Incoming {
290 return self;
291 }
292
293 if self.topics.iter().any(|t| t == &topic) {
294 return self;
295 }
296
297 let mut topics = self.topics;
298 topics.push(topic).ok();
299 Self { topics }
300 }
301
302 pub fn topics(self) -> Result<heapless::Vec<heapless::String<256>, N>, Error> {
303 self.topics
304 .iter()
305 .map(|topic| topic.clone().format())
306 .collect()
307 }
308
309 pub fn send<M: Mqtt>(self, mqtt: &M) -> Result<(), Error> {
310 if self.topics.is_empty() {
311 return Ok(());
312 }
313
314 let topic_paths = self.topics()?;
315 let topics: heapless::Vec<_, N> = topic_paths.iter().map(|s| s.as_str()).collect();
316
317 for t in topics.chunks(5) {
318 mqtt.unsubscribe(t)?;
319 }
320
321 Ok(())
322 }
323}