diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 6738b0b..5a759e4 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -8,7 +8,6 @@ on: jobs: build: - runs-on: ubuntu-latest steps: @@ -17,3 +16,25 @@ jobs: run: cargo build --verbose - name: Run tests run: cargo test --verbose + - name: Build serde + run: cargo build --verbose --features=derive + - name: Run tests serde + run: cargo test --verbose --features=derive + - name: Build no_std + run: cargo build --verbose --no-default-features + - name: Run tests no_std + run: cargo test --verbose --no-default-features + - name: Build no_std + run: cargo build --verbose --no-default-features --features=derive + - name: Run tests no_std + run: cargo test --verbose --no-default-features --features=derive + - name: Build defmt + run: cargo build --verbose --no-default-features --features=defmt + - name: Run tests defmt + run: cargo test --verbose --no-default-features --features=defmt + - name: Build defmt derive + run: cargo build --verbose --no-default-features --features=defmt --features=derive + - name: Run tests defmt derive + run: cargo test --verbose --no-default-features --features=defmt --features=derive + + diff --git a/Cargo.toml b/Cargo.toml index 2bc1424..0638cf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,13 +18,15 @@ license = "Apache-2.0" default = ["std"] # Implements serde::{Serialize,Deserialize} on mqttrs::Pid. -derive = ["serde"] -std = ["bytes", "bytes/std", "serde/std"] +derive = ["serde", "heapless/serde"] +std = ["bytes/std", "serde/std"] +defmt = ["dep:defmt", "heapless/defmt-03"] [dependencies] -bytes = { version = "1.0", default-features = false, optional = true } +bytes = { version = "1.0", default-features = false} serde = { version = "1.0", features = ["derive"], optional = true } -heapless = "0.7" +heapless = { version = "0.8" } +defmt = { version = "0.3.10", optional = true } [dev-dependencies] proptest = "0.10.0" diff --git a/src/connect.rs b/src/connect.rs index ef627e8..5991a46 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -1,10 +1,20 @@ +#[cfg(feature = "defmt")] +use defmt::Format; use crate::{decoder::*, encoder::*, *}; +#[cfg(not(feature = "std"))] +use heapless::String; +#[cfg(feature = "std")] +use std::string::String; +use core::str::FromStr; + /// Protocol version. /// /// Sent in [`Connect`] packet. /// /// [`Connect`]: struct.Connect.html +/// +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Protocol { /// [MQTT 3.1.1] is the most commonly implemented version. [MQTT 5] isn't yet supported my by @@ -23,8 +33,8 @@ impl Protocol { match (name, level) { ("MQIsdp", 3) => Ok(Protocol::MQIsdp), ("MQTT", 4) => Ok(Protocol::MQTT311), - _ => Err(Error::InvalidProtocol(name.into(), level)), - } + _ => Err(Error::InvalidProtocol(String::from_str(name).unwrap(), 0)), + } } pub(crate) fn from_buffer<'a>(buf: &'a [u8], offset: &mut usize) -> Result { let protocol_name = read_str(buf, offset)?; @@ -61,6 +71,7 @@ impl Protocol { /// /// [Connect]: struct.Connect.html /// [MQTT 3.1.3.3]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718031 +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, PartialEq)] pub struct LastWill<'a> { pub topic: &'a str, @@ -75,6 +86,7 @@ pub struct LastWill<'a> { /// /// [Connack]: struct.Connack.html /// [MQTT 3.2.2.3]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718035 +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, Copy, PartialEq)] pub enum ConnectReturnCode { Accepted, @@ -111,6 +123,7 @@ impl ConnectReturnCode { /// Connect packet ([MQTT 3.1]). /// /// [MQTT 3.1]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028 +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, PartialEq)] pub struct Connect<'a> { pub protocol: Protocol, @@ -125,6 +138,7 @@ pub struct Connect<'a> { /// Connack packet ([MQTT 3.2]). /// /// [MQTT 3.2]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718033 +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, Copy, PartialEq)] pub struct Connack { pub session_present: bool, diff --git a/src/decoder_test.rs b/src/decoder_test.rs index b416433..00894a0 100644 --- a/src/decoder_test.rs +++ b/src/decoder_test.rs @@ -1,6 +1,7 @@ use crate::*; use bytes::BytesMut; use subscribe::LimitedString; +use core::str::FromStr; macro_rules! header { ($t:ident, $d:expr, $q:ident, $r:expr) => { @@ -421,6 +422,7 @@ fn test_pub_comp() { }; } +#[cfg(feature = "std")] #[test] fn test_subscribe() { let mut data: &[u8] = &[ @@ -439,6 +441,25 @@ fn test_subscribe() { } } +#[cfg(not(feature = "std"))] +#[test] +fn test_subscribe() { + let mut data: &[u8] = &[ + 0b10000010, 8, 0, 10, 0, 3, 'a' as u8, '/' as u8, 'b' as u8, 0, + ]; + match decode_slice(&mut data) { + Ok(Some(Packet::Subscribe(s))) => { + assert_eq!(s.pid.get(), 10); + let t = SubscribeTopic { + topic_path: LimitedString::from_str("a/b").unwrap(), + qos: QoS::AtMostOnce, + }; + assert_eq!(s.topics.get(0), Some(&t)); + } + other => panic!("Failed decode: {:?}", other), + } +} + #[test] fn test_suback() { let mut data: &[u8] = &[0b10010000, 3, 0, 10, 0b00000010]; @@ -454,6 +475,7 @@ fn test_suback() { } } +#[cfg(feature = "std")] #[test] fn test_unsubscribe() { let mut data: &[u8] = &[0b10100010, 5, 0, 10, 0, 1, 'a' as u8]; @@ -466,6 +488,19 @@ fn test_unsubscribe() { } } +#[cfg(not(feature = "std"))] +#[test] +fn test_unsubscribe() { + let mut data: &[u8] = &[0b10100010, 5, 0, 10, 0, 1, 'a' as u8]; + match decode_slice(&mut data) { + Ok(Some(Packet::Unsubscribe(a))) => { + assert_eq!(a.pid.get(), 10); + assert_eq!(a.topics.get(0), Some(&LimitedString::from_str("a").unwrap())); + } + other => panic!("Failed decode: {:?}", other), + } +} + #[test] fn test_unsub_ack() { let mut data: &[u8] = &[0b10110000, 2, 0, 10]; diff --git a/src/encoder_test.rs b/src/encoder_test.rs index 918a6d3..b79c941 100644 --- a/src/encoder_test.rs +++ b/src/encoder_test.rs @@ -1,6 +1,7 @@ use crate::*; use core::convert::TryFrom; use subscribe::{LimitedString, LimitedVec}; +use core::str::FromStr; #[cfg(feature = "std")] use bytes::BytesMut; @@ -130,7 +131,7 @@ fn test_pubcomp() { // assert_decode!(Packet::Pubcomp(_), &packet); assert_decode_slice!(Packet::Pubcomp(_), &packet, 4); } - +#[cfg(feature = "std")] #[test] fn test_subscribe() { let stopic = SubscribeTopic { @@ -143,6 +144,19 @@ fn test_subscribe() { assert_decode_slice!(Packet::Subscribe(_), &packet, 10); } +#[cfg(not(feature = "std"))] +#[test] +fn test_subscribe() { + let stopic = SubscribeTopic { + topic_path: LimitedString::from_str("a/b").unwrap(), + qos: QoS::ExactlyOnce, + }; + let topics: LimitedVec = [stopic].iter().cloned().collect(); + let packet = Subscribe::new(Pid::try_from(345).unwrap(), topics).into(); + // assert_decode!(Packet::Subscribe(_), &packet); + assert_decode_slice!(Packet::Subscribe(_), &packet, 10); +} + #[test] fn test_suback() { let return_codes = [SubscribeReturnCodes::Success(QoS::ExactlyOnce)] @@ -156,7 +170,10 @@ fn test_suback() { #[test] fn test_unsubscribe() { + #[cfg(feature = "std")] let topics: LimitedVec = [LimitedString::from("a/b")].iter().cloned().collect(); + #[cfg(not(feature = "std"))] + let topics: LimitedVec = [LimitedString::from_str("a/b").unwrap()].iter().cloned().collect(); let packet = Unsubscribe::new(Pid::try_from(12321).unwrap(), topics).into(); // assert_decode!(Packet::Unsubscribe(_), &packet); diff --git a/src/packet.rs b/src/packet.rs index d4c7a58..7c3b66a 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "defmt")] +use defmt::Format; + use crate::*; /// Base enum for all MQTT packet types. @@ -24,6 +27,7 @@ use crate::*; /// /// [`encode()`]: fn.encode.html /// [`decode_slice()`]: fn.decode_slice.html +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, PartialEq)] pub enum Packet<'a> { /// [MQTT 3.1](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718028) @@ -107,6 +111,7 @@ packet_from_borrowed!(Connect, Publish); packet_from!(Suback, Connack, Subscribe, Unsubscribe); /// Packet type variant, without the associated data. +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum PacketType { Connect, diff --git a/src/publish.rs b/src/publish.rs index 54abbcc..f2b60b5 100644 --- a/src/publish.rs +++ b/src/publish.rs @@ -1,8 +1,11 @@ +#[cfg(feature = "defmt")] +use defmt::Format; use crate::{decoder::*, encoder::*, *}; /// Publish packet ([MQTT 3.3]). /// /// [MQTT 3.3]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718037 +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, PartialEq)] pub struct Publish<'a> { pub dup: bool, diff --git a/src/subscribe.rs b/src/subscribe.rs index b449db1..2aa7521 100644 --- a/src/subscribe.rs +++ b/src/subscribe.rs @@ -1,22 +1,27 @@ +#[cfg(feature = "defmt")] +use defmt::Format; use crate::{decoder::*, encoder::*, *}; #[cfg(feature = "derive")] use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] -pub(crate) type LimitedVec = std::vec::Vec; +pub type LimitedVec = std::vec::Vec; #[cfg(not(feature = "std"))] -pub(crate) type LimitedVec = heapless::Vec; +pub type LimitedVec = heapless::Vec; #[cfg(feature = "std")] -pub(crate) type LimitedString = std::string::String; +pub type LimitedString = std::string::String; #[cfg(not(feature = "std"))] -pub(crate) type LimitedString = heapless::String<256>; +pub type LimitedString = heapless::String<256>; + +use core::str::FromStr; /// Subscribe topic. /// /// [Subscribe] packets contain a `Vec` of those. /// /// [Subscribe]: struct.Subscribe.html +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, PartialEq)] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] pub struct SubscribeTopic { @@ -26,7 +31,7 @@ pub struct SubscribeTopic { impl SubscribeTopic { pub(crate) fn from_buffer(buf: &[u8], offset: &mut usize) -> Result { - let topic_path = LimitedString::from(read_str(buf, offset)?); + let topic_path = LimitedString::from_str(read_str(buf, offset)?).unwrap(); let qos = QoS::from_u8(buf[*offset])?; *offset += 1; Ok(SubscribeTopic { topic_path, qos }) @@ -38,6 +43,7 @@ impl SubscribeTopic { /// [Suback] packets contain a `Vec` of those. /// /// [Suback]: struct.Subscribe.html +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum SubscribeReturnCodes { Success(QoS), @@ -67,6 +73,7 @@ impl SubscribeReturnCodes { /// Subscribe packet ([MQTT 3.8]). /// /// [MQTT 3.8]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718063 +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, PartialEq)] pub struct Subscribe { pub pid: Pid, @@ -76,6 +83,7 @@ pub struct Subscribe { /// Subsack packet ([MQTT 3.9]). /// /// [MQTT 3.9]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718068 +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, PartialEq)] pub struct Suback { pub pid: Pid, @@ -85,6 +93,7 @@ pub struct Suback { /// Unsubscribe packet ([MQTT 3.10]). /// /// [MQTT 3.10]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718072 +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, PartialEq)] pub struct Unsubscribe { pub pid: Pid, @@ -155,7 +164,7 @@ impl Unsubscribe { let mut topics = LimitedVec::new(); while *offset < payload_end { - let _res = topics.push(LimitedString::from(read_str(buf, offset)?)); + let _res = topics.push(LimitedString::from_str(read_str(buf, offset)?).unwrap()); #[cfg(not(feature = "std"))] _res.map_err(|_| Error::InvalidLength)?; diff --git a/src/utils.rs b/src/utils.rs index c0d5f59..47357d0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,7 @@ +#[cfg(feature = "defmt")] +use defmt::{Format}; + + use crate::encoder::write_u16; use core::{convert::TryFrom, fmt, num::NonZeroU16}; @@ -15,6 +19,9 @@ use std::{ /// /// [`encode()`]: fn.encode.html /// [`decode()`]: fn.decode.html + + +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, PartialEq, Eq)] pub enum Error { /// Not enough space in the write buffer. @@ -40,7 +47,7 @@ pub enum Error { /// length rather than a buffer size issue. InvalidLength, /// Trying to decode a non-utf8 string. - InvalidString(core::str::Utf8Error), + InvalidString(#[cfg_attr(feature = "defmt",defmt(Debug2Format))] core::str::Utf8Error), /// Catch-all error when converting from `std::io::Error`. /// /// Note: Only available when std is available. @@ -106,6 +113,8 @@ impl From for Error { /// [`QoS::AtLeastOne` or `QoS::ExactlyOnce`]: enum.QoS.html /// [MQTT-2.3.1-1]: https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718025 /// [MQTT-2.2.1-3]: https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901026 + +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] pub struct Pid(NonZeroU16); @@ -186,6 +195,7 @@ impl TryFrom for Pid { /// Packet delivery [Quality of Service] level. /// /// [Quality of Service]: http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718099 +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] pub enum QoS { @@ -223,6 +233,7 @@ impl QoS { /// [`Publish`]: struct.Publish.html /// [`QoS`]: enum.QoS.html /// [`Pid`]: struct.Pid.html +#[cfg_attr(feature = "defmt",derive(Format))] #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "derive", derive(Serialize, Deserialize))] pub enum QosPid {