#![cfg_attr(not(feature = "std"), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::{string::String, vec::Vec};
pub mod filter;
#[cfg(feature = "alloc")]
mod ledger;
#[cfg(feature = "alloc")]
mod role;
#[cfg(feature = "alloc")]
pub use ledger::FilterLedger;
#[cfg(feature = "alloc")]
pub use role::{Pair, Receipt, Role};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Handled {
Consumed,
Ignored,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u8)]
pub enum QoS {
AtMostOnce = 0,
AtLeastOnce = 1,
ExactlyOnce = 2,
}
impl QoS {
pub const fn from_u8(value: u8) -> Option<Self> {
match value {
0 => Some(Self::AtMostOnce),
1 => Some(Self::AtLeastOnce),
2 => Some(Self::ExactlyOnce),
_ => None,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Instant(pub u64);
impl Instant {
pub const fn saturating_since(self, earlier: Instant) -> u64 {
self.0.saturating_sub(earlier.0)
}
#[must_use]
pub const fn add_ms(self, ms: u64) -> Instant {
Instant(self.0.saturating_add(ms))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct Message<'a> {
pub topic: &'a str,
pub payload: &'a [u8],
pub retain: Option<bool>,
}
impl<'a> Message<'a> {
pub const fn new(topic: &'a str, payload: &'a [u8], retain: Option<bool>) -> Self {
Self {
topic,
payload,
retain,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Link {
#[non_exhaustive]
Up {
session_present: bool,
},
Down,
}
impl Link {
pub const fn up(session_present: bool) -> Self {
Self::Up { session_present }
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Command {
#[non_exhaustive]
Subscribe {
filters: Vec<(String, QoS)>,
},
#[non_exhaustive]
Unsubscribe {
filters: Vec<String>,
},
#[non_exhaustive]
Publish {
topic: String,
payload: Vec<u8>,
qos: QoS,
retain: bool,
},
}
#[cfg(feature = "alloc")]
impl Command {
pub fn subscribe(filters: impl Into<Vec<(String, QoS)>>) -> Self {
Self::Subscribe {
filters: filters.into(),
}
}
pub fn unsubscribe(filters: impl Into<Vec<String>>) -> Self {
Self::Unsubscribe {
filters: filters.into(),
}
}
pub fn publish(
topic: impl Into<String>,
payload: impl Into<Vec<u8>>,
qos: QoS,
retain: bool,
) -> Self {
Self::Publish {
topic: topic.into(),
payload: payload.into(),
qos,
retain,
}
}
}
#[cfg(feature = "alloc")]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct LastWill {
pub topic: String,
pub payload: Vec<u8>,
pub qos: QoS,
pub retain: bool,
}
#[cfg(feature = "alloc")]
impl LastWill {
pub fn new(
topic: impl Into<String>,
payload: impl Into<Vec<u8>>,
qos: QoS,
retain: bool,
) -> Self {
Self {
topic: topic.into(),
payload: payload.into(),
qos,
retain,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn qos_wire_values_round_trip() {
for q in [QoS::AtMostOnce, QoS::AtLeastOnce, QoS::ExactlyOnce] {
assert_eq!(QoS::from_u8(q as u8), Some(q));
}
assert_eq!(QoS::from_u8(3), None);
assert_eq!(QoS::from_u8(u8::MAX), None);
assert!(QoS::AtMostOnce < QoS::AtLeastOnce);
assert!(QoS::AtLeastOnce < QoS::ExactlyOnce);
}
#[test]
fn instant_saturates() {
let a = Instant(1_000);
assert_eq!(a.add_ms(500), Instant(1_500));
assert_eq!(a.add_ms(500).saturating_since(a), 500);
assert_eq!(a.saturating_since(a.add_ms(500)), 0);
assert_eq!(a.saturating_since(a), 0);
assert_eq!(Instant(u64::MAX - 1).add_ms(10), Instant(u64::MAX));
assert_eq!(Instant(u64::MAX).saturating_since(Instant(0)), u64::MAX);
assert_eq!(Instant::default(), Instant(0));
}
#[cfg(feature = "alloc")]
#[test]
fn constructors_fill_every_field() {
use alloc::{string::ToString, vec};
assert_eq!(
Command::publish("t", b"p".to_vec(), QoS::AtLeastOnce, true),
Command::Publish {
topic: "t".to_string(),
payload: b"p".to_vec(),
qos: QoS::AtLeastOnce,
retain: true,
}
);
let filters = vec![("a/#".to_string(), QoS::ExactlyOnce)];
assert_eq!(
Command::subscribe(filters.clone()),
Command::Subscribe { filters }
);
assert_eq!(
Command::unsubscribe(vec!["a/#".to_string()]),
Command::Unsubscribe {
filters: vec!["a/#".to_string()],
}
);
let will = LastWill::new("w", b"lost".to_vec(), QoS::AtMostOnce, false);
assert_eq!(
(
will.topic.as_str(),
will.payload.as_slice(),
will.qos,
will.retain
),
("w", &b"lost"[..], QoS::AtMostOnce, false)
);
assert_eq!(
Link::up(true),
Link::Up {
session_present: true
}
);
assert_eq!(
Message::new("t", b"p", Some(false)),
Message {
topic: "t",
payload: b"p",
retain: Some(false),
}
);
}
#[test]
fn message_is_copy() {
let m = Message {
topic: "ebus/5/d/$state",
payload: b"ready",
retain: None,
};
let n = m;
assert_eq!(m, n);
}
}
#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;