1#![cfg_attr(not(feature = "std"), no_std)]
28#![cfg_attr(docsrs, feature(doc_cfg))]
29
30#[cfg(feature = "alloc")]
31extern crate alloc;
32
33#[cfg(feature = "alloc")]
34use alloc::{string::String, vec::Vec};
35
36pub mod filter;
37#[cfg(feature = "alloc")]
38mod ledger;
39#[cfg(feature = "alloc")]
40mod role;
41
42#[cfg(feature = "alloc")]
43pub use ledger::FilterLedger;
44#[cfg(feature = "alloc")]
45pub use role::{Pair, Receipt, Role};
46
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
49pub enum Handled {
50 Consumed,
52 Ignored,
54}
55
56#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
61#[repr(u8)]
62pub enum QoS {
63 AtMostOnce = 0,
65 AtLeastOnce = 1,
67 ExactlyOnce = 2,
69}
70
71impl QoS {
72 pub const fn from_u8(value: u8) -> Option<Self> {
74 match value {
75 0 => Some(Self::AtMostOnce),
76 1 => Some(Self::AtLeastOnce),
77 2 => Some(Self::ExactlyOnce),
78 _ => None,
79 }
80 }
81}
82
83#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
88pub struct Instant(pub u64);
89
90impl Instant {
91 pub const fn saturating_since(self, earlier: Instant) -> u64 {
93 self.0.saturating_sub(earlier.0)
94 }
95
96 #[must_use]
98 pub const fn add_ms(self, ms: u64) -> Instant {
99 Instant(self.0.saturating_add(ms))
100 }
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
107#[non_exhaustive]
108pub struct Message<'a> {
109 pub topic: &'a str,
111 pub payload: &'a [u8],
113 pub retain: Option<bool>,
115}
116
117impl<'a> Message<'a> {
118 pub const fn new(topic: &'a str, payload: &'a [u8], retain: Option<bool>) -> Self {
120 Self {
121 topic,
122 payload,
123 retain,
124 }
125 }
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
132#[non_exhaustive]
133pub enum Link {
134 #[non_exhaustive]
138 Up {
139 session_present: bool,
142 },
143 Down,
145}
146
147impl Link {
148 pub const fn up(session_present: bool) -> Self {
150 Self::Up { session_present }
151 }
152}
153
154#[cfg(feature = "alloc")]
160#[derive(Clone, Debug, PartialEq, Eq, Hash)]
161#[non_exhaustive]
162pub enum Command {
163 #[non_exhaustive]
165 Subscribe {
166 filters: Vec<(String, QoS)>,
168 },
169 #[non_exhaustive]
171 Unsubscribe {
172 filters: Vec<String>,
174 },
175 #[non_exhaustive]
177 Publish {
178 topic: String,
180 payload: Vec<u8>,
182 qos: QoS,
184 retain: bool,
186 },
187}
188
189#[cfg(feature = "alloc")]
190impl Command {
191 pub fn subscribe(filters: impl Into<Vec<(String, QoS)>>) -> Self {
193 Self::Subscribe {
194 filters: filters.into(),
195 }
196 }
197
198 pub fn unsubscribe(filters: impl Into<Vec<String>>) -> Self {
200 Self::Unsubscribe {
201 filters: filters.into(),
202 }
203 }
204
205 pub fn publish(
207 topic: impl Into<String>,
208 payload: impl Into<Vec<u8>>,
209 qos: QoS,
210 retain: bool,
211 ) -> Self {
212 Self::Publish {
213 topic: topic.into(),
214 payload: payload.into(),
215 qos,
216 retain,
217 }
218 }
219}
220
221#[cfg(feature = "alloc")]
225#[derive(Clone, Debug, PartialEq, Eq, Hash)]
226#[non_exhaustive]
227pub struct LastWill {
228 pub topic: String,
230 pub payload: Vec<u8>,
232 pub qos: QoS,
234 pub retain: bool,
236}
237
238#[cfg(feature = "alloc")]
239impl LastWill {
240 pub fn new(
242 topic: impl Into<String>,
243 payload: impl Into<Vec<u8>>,
244 qos: QoS,
245 retain: bool,
246 ) -> Self {
247 Self {
248 topic: topic.into(),
249 payload: payload.into(),
250 qos,
251 retain,
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn qos_wire_values_round_trip() {
262 for q in [QoS::AtMostOnce, QoS::AtLeastOnce, QoS::ExactlyOnce] {
263 assert_eq!(QoS::from_u8(q as u8), Some(q));
264 }
265 assert_eq!(QoS::from_u8(3), None);
266 assert_eq!(QoS::from_u8(u8::MAX), None);
267 assert!(QoS::AtMostOnce < QoS::AtLeastOnce);
268 assert!(QoS::AtLeastOnce < QoS::ExactlyOnce);
269 }
270
271 #[test]
272 fn instant_saturates() {
273 let a = Instant(1_000);
274 assert_eq!(a.add_ms(500), Instant(1_500));
275 assert_eq!(a.add_ms(500).saturating_since(a), 500);
276 assert_eq!(a.saturating_since(a.add_ms(500)), 0);
277 assert_eq!(a.saturating_since(a), 0);
278 assert_eq!(Instant(u64::MAX - 1).add_ms(10), Instant(u64::MAX));
279 assert_eq!(Instant(u64::MAX).saturating_since(Instant(0)), u64::MAX);
280 assert_eq!(Instant::default(), Instant(0));
281 }
282
283 #[cfg(feature = "alloc")]
284 #[test]
285 fn constructors_fill_every_field() {
286 use alloc::{string::ToString, vec};
287 assert_eq!(
288 Command::publish("t", b"p".to_vec(), QoS::AtLeastOnce, true),
289 Command::Publish {
290 topic: "t".to_string(),
291 payload: b"p".to_vec(),
292 qos: QoS::AtLeastOnce,
293 retain: true,
294 }
295 );
296 let filters = vec![("a/#".to_string(), QoS::ExactlyOnce)];
297 assert_eq!(
298 Command::subscribe(filters.clone()),
299 Command::Subscribe { filters }
300 );
301 assert_eq!(
302 Command::unsubscribe(vec!["a/#".to_string()]),
303 Command::Unsubscribe {
304 filters: vec!["a/#".to_string()],
305 }
306 );
307 let will = LastWill::new("w", b"lost".to_vec(), QoS::AtMostOnce, false);
308 assert_eq!(
309 (
310 will.topic.as_str(),
311 will.payload.as_slice(),
312 will.qos,
313 will.retain
314 ),
315 ("w", &b"lost"[..], QoS::AtMostOnce, false)
316 );
317 assert_eq!(
318 Link::up(true),
319 Link::Up {
320 session_present: true
321 }
322 );
323 assert_eq!(
324 Message::new("t", b"p", Some(false)),
325 Message {
326 topic: "t",
327 payload: b"p",
328 retain: Some(false),
329 }
330 );
331 }
332
333 #[test]
334 fn message_is_copy() {
335 let m = Message {
336 topic: "ebus/5/d/$state",
337 payload: b"ready",
338 retain: None,
339 };
340 let n = m;
341 assert_eq!(m, n);
342 }
343}
344
345#[cfg(doctest)]
346#[doc = include_str!("../README.md")]
347struct ReadmeDoctests;