From 50bf2bc8d2a55984a5e20e80057fcaea077f3cb5 Mon Sep 17 00:00:00 2001 From: GunnarMorrigan <13799935+GunnarMorrigan@users.noreply.github.com> Date: Wed, 31 Jan 2024 21:42:31 +0100 Subject: [PATCH] Increase test coverage, fix bug in disconnect writing of DisconnectProperties --- mqrstt/src/client.rs | 350 ++++++++++++++++++++--------- mqrstt/src/packets/disconnect.rs | 104 +++++++-- mqrstt/src/packets/reason_codes.rs | 5 +- 3 files changed, 331 insertions(+), 128 deletions(-) diff --git a/mqrstt/src/client.rs b/mqrstt/src/client.rs index ad5d743..bb9c1c4 100644 --- a/mqrstt/src/client.rs +++ b/mqrstt/src/client.rs @@ -152,38 +152,28 @@ impl MqttClient { /// subscription_id: Some(1), /// user_properties: vec![], /// }; + /// + /// let sub_properties_clone = sub_properties.clone(); /// /// // retain_handling: RetainHandling::ZERO, retain_as_publish: false, no_local: false, qos: QoS::AtMostOnce, /// mqtt_client.subscribe_with_properties("test/topic", sub_properties).await; /// - /// # let sub_properties = SubscribeProperties{ - /// # subscription_id: Some(1), - /// # user_properties: vec![], - /// # }; + /// # let sub_properties = sub_properties_clone.clone(); /// /// // retain_handling: RetainHandling::ZERO, retain_as_publish: false, no_local: false, qos: QoS::ExactlyOnce, /// mqtt_client.subscribe_with_properties(("test/topic", QoS::ExactlyOnce), sub_properties).await; /// - /// # let sub_properties = SubscribeProperties{ - /// # subscription_id: Some(1), - /// # user_properties: vec![], - /// # }; + /// # let sub_properties = sub_properties_clone.clone(); /// /// let vec = vec![("test/topic1", QoS::ExactlyOnce), ("test/topic2", QoS::ExactlyOnce)]; /// mqtt_client.subscribe_with_properties(vec, sub_properties).await; /// - /// # let sub_properties = SubscribeProperties{ - /// # subscription_id: Some(1), - /// # user_properties: vec![], - /// # }; + /// # let sub_properties = sub_properties_clone.clone(); /// /// let vec = [("test/topic1", QoS::ExactlyOnce), ("test/topic2", QoS::ExactlyOnce)]; /// mqtt_client.subscribe_with_properties(vec.as_slice(), sub_properties).await; /// - /// # let sub_properties = SubscribeProperties{ - /// # subscription_id: Some(1), - /// # user_properties: vec![], - /// # }; + /// # let sub_properties = sub_properties_clone.clone(); /// /// let sub_options = SubscriptionOptions{ /// retain_handling: RetainHandling::ZERO, @@ -278,43 +268,29 @@ impl MqttClient { /// correlation_data: Some("correlation_data".into()), /// ..Default::default() /// }; + /// + /// # let properties_clone = properties.clone(); /// /// // publish a message with QoS 0, without a packet identifier /// mqtt_client.publish_with_properties("test/topic", QoS::AtMostOnce, false, Bytes::from("Hello world"), properties).await; /// - /// # let properties = PublishProperties{ - /// # response_topic: Some("response/topic".into()), - /// # correlation_data: Some("correlation_data".into()), - /// # ..Default::default() - /// # }; + /// # let properties = properties_clone.clone(); /// /// // publish a message with QoS 1, with a packet identifier /// mqtt_client.publish_with_properties("test/topic", QoS::AtLeastOnce, false, Bytes::from("Hello world"), properties).await; /// - /// # let properties = PublishProperties{ - /// # response_topic: Some("response/topic".into()), - /// # correlation_data: Some("correlation_data".into()), - /// # ..Default::default() - /// # }; + /// # let properties = properties_clone.clone(); /// /// // publish a message with QoS 2, with a packet identifier /// mqtt_client.publish_with_properties("test/topic", QoS::ExactlyOnce, false, Bytes::from("Hello world"), properties).await; /// - /// # let properties = PublishProperties{ - /// # response_topic: Some("response/topic".into()), - /// # correlation_data: Some("correlation_data".into()), - /// # ..Default::default() - /// # }; + /// # let properties = properties_clone.clone(); /// /// // publish a message with QoS 1, with a packet identifier, and the "retain" flag set /// let payload = "Hello World!".as_bytes(); /// mqtt_client.publish_with_properties("test/topic", QoS::AtLeastOnce, true, payload, properties).await; /// - /// # let properties = PublishProperties{ - /// # response_topic: Some("response/topic".into()), - /// # correlation_data: Some("correlation_data".into()), - /// # ..Default::default() - /// # }; + /// # let properties = properties_clone.clone(); /// /// // publish a message with QoS 1, with a packet identifier, and the "retain" flag set /// let payload = "Hello World!".as_bytes().to_vec(); @@ -581,38 +557,27 @@ impl MqttClient { /// subscription_id: Some(1), /// user_properties: vec![], /// }; - /// + /// # let sub_properties_clone = sub_properties.clone(); + /// /// // retain_handling: RetainHandling::ZERO, retain_as_publish: false, no_local: false, qos: QoS::AtMostOnce, /// mqtt_client.subscribe_with_properties_blocking("test/topic", sub_properties).unwrap(); /// - /// # let sub_properties = SubscribeProperties{ - /// # subscription_id: Some(1), - /// # user_properties: vec![], - /// # }; + /// # let sub_properties = sub_properties_clone.clone(); /// /// // retain_handling: RetainHandling::ZERO, retain_as_publish: false, no_local: false, qos: QoS::ExactlyOnce, /// mqtt_client.subscribe_with_properties_blocking(("test/topic", QoS::ExactlyOnce), sub_properties).unwrap(); /// - /// # let sub_properties = SubscribeProperties{ - /// # subscription_id: Some(1), - /// # user_properties: vec![], - /// # }; + /// # let sub_properties = sub_properties_clone.clone(); /// /// let vec = vec![("test/topic1", QoS::ExactlyOnce), ("test/topic2", QoS::ExactlyOnce)]; /// mqtt_client.subscribe_with_properties_blocking(vec, sub_properties).unwrap(); /// - /// # let sub_properties = SubscribeProperties{ - /// # subscription_id: Some(1), - /// # user_properties: vec![], - /// # }; + /// # let sub_properties = sub_properties_clone.clone(); /// /// let vec = [("test/topic1", QoS::ExactlyOnce), ("test/topic2", QoS::ExactlyOnce)]; /// mqtt_client.subscribe_with_properties_blocking(vec.as_slice(), sub_properties).unwrap(); /// - /// # let sub_properties = SubscribeProperties{ - /// # subscription_id: Some(1), - /// # user_properties: vec![], - /// # }; + /// # let sub_properties = sub_properties_clone.clone(); /// /// let sub_options = SubscriptionOptions{ /// retain_handling: RetainHandling::ZERO, @@ -711,53 +676,35 @@ impl MqttClient { /// correlation_data: Some("correlation_data".into()), /// ..Default::default() /// }; + /// + /// # let properties_clone = properties.clone(); /// /// // publish a message with QoS 0, without a packet identifier /// mqtt_client.publish_with_properties_blocking("test/topic", QoS::AtMostOnce, false, Bytes::from("Hello world"), properties).unwrap(); /// - /// # let properties = PublishProperties{ - /// # response_topic: Some("response/topic".into()), - /// # correlation_data: Some("correlation_data".into()), - /// # ..Default::default() - /// # }; + /// # let properties = properties_clone.clone(); /// /// // publish a message with QoS 1, with a packet identifier /// mqtt_client.publish_with_properties_blocking("test/topic", QoS::AtLeastOnce, false, Bytes::from("Hello world"), properties).unwrap(); /// - /// # let properties = PublishProperties{ - /// # response_topic: Some("response/topic".into()), - /// # correlation_data: Some("correlation_data".into()), - /// # ..Default::default() - /// # }; + /// # let properties = properties_clone.clone(); /// /// // publish a message with QoS 2, with a packet identifier /// mqtt_client.publish_with_properties_blocking("test/topic", QoS::ExactlyOnce, false, Bytes::from("Hello world"), properties).unwrap(); /// - /// # let properties = PublishProperties{ - /// # response_topic: Some("response/topic".into()), - /// # correlation_data: Some("correlation_data".into()), - /// # ..Default::default() - /// # }; + /// # let properties = properties_clone.clone(); /// /// // publish a message with QoS 1, with a packet identifier, and the "retain" flag set /// let payload = "Hello World!".as_bytes(); /// mqtt_client.publish_with_properties_blocking("test/topic", QoS::AtLeastOnce, true, payload, properties).unwrap(); /// - /// # let properties = PublishProperties{ - /// # response_topic: Some("response/topic".into()), - /// # correlation_data: Some("correlation_data".into()), - /// # ..Default::default() - /// # }; + /// # let properties = properties_clone.clone(); /// /// // publish a message with QoS 1, with a packet identifier, and the "retain" flag set /// let payload = "Hello World!".as_bytes().to_vec(); /// mqtt_client.publish_with_properties_blocking("test/topic", QoS::AtMostOnce, true, payload, properties).unwrap(); /// /// # }); - /// - /// - - /// /// ``` pub fn publish_with_properties_blocking, P: Into>(&self, topic: T, qos: QoS, retain: bool, payload: P, properties: PublishProperties) -> Result<(), ClientError> { let pkid = match qos { @@ -951,7 +898,7 @@ mod tests { use crate::{ error::{ClientError, PacketValidationError}, - packets::{reason_codes::DisconnectReasonCode, DisconnectProperties, Packet, PacketType, QoS, UnsubscribeProperties}, + packets::{reason_codes::DisconnectReasonCode, DisconnectProperties, Packet, PacketType, Publish, QoS, Subscribe, SubscribeProperties, UnsubscribeProperties}, }; use super::MqttClient; @@ -996,29 +943,132 @@ mod tests { let _ = mqtt_client.subscribe(("final/test/topic", sub_options)).await; } + #[tokio::test] + async fn test_subscribe_with_properties() { + let (mqtt_client, client_to_handler_r, to_network_r) = create_new_test_client(); + + let sub_properties = SubscribeProperties{ + subscription_id: Some(1), + user_properties: vec![], + }; + + // retain_handling: RetainHandling::ZERO, retain_as_publish: false, no_local: false, qos: QoS::AtMostOnce, + let res = mqtt_client.subscribe_with_properties("test/topic", sub_properties.clone()).await; + + assert!(res.is_ok()); + let packet = client_to_handler_r.recv().await.unwrap(); + // assert!(matches!(packet, Packet::Subscribe(sub) if sub.properties.subscription_id == Some(1))); + assert!(matches!(packet, Packet::Subscribe(sub) if sub.properties == sub_properties && sub.topics[0].0.as_ref() == "test/topic")); + + std::hint::black_box((mqtt_client, client_to_handler_r, to_network_r)); + } + + #[test] + + fn test_subscribe_blocking() { + let (client, client_to_handler_r, to_network_r) = create_new_test_client(); + + // retain_handling: RetainHandling::ZERO, retain_as_publish: false, no_local: false, qos: QoS::AtMostOnce, + client.subscribe_blocking("test/topic").unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Subscribe(sub) if sub.topics[0].0.as_ref() == "test/topic")); + + // retain_handling: RetainHandling::ZERO, retain_as_publish: false, no_local: false, qos: QoS::ExactlyOnce, + client.subscribe_blocking(("test/topic", QoS::ExactlyOnce)).unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Subscribe(sub) if sub.topics[0].0.as_ref() == "test/topic")); + + let vec = vec![("test/topic1", QoS::ExactlyOnce), ("test/topic2", QoS::AtMostOnce)]; + client.subscribe_blocking(vec).unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Subscribe(sub) if sub.topics[0].0.as_ref() == "test/topic1" && sub.topics[1].0.as_ref() == "test/topic2")); + + let sub_options = crate::packets::SubscriptionOptions { + retain_handling: crate::packets::RetainHandling::TWO, + retain_as_publish: false, + no_local: false, + qos: QoS::AtLeastOnce, + }; + client.subscribe_blocking(("final/test/topic", sub_options.clone())).unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Subscribe(sub) if sub.topics[0].0.as_ref() == "final/test/topic" && sub.topics[0].1 == sub_options)); + + std::hint::black_box((client, client_to_handler_r, to_network_r)); + } + #[tokio::test] async fn test_unsubscribe() { - let (mqtt_client, _client_to_handler_r, _) = create_new_test_client(); + let (client, client_to_handler_r, to_network_r) = create_new_test_client(); // Unsubscribe from a single topic specified as a string: let topic = "test/topic"; - let _ = mqtt_client.unsubscribe(topic).await; + client.unsubscribe(topic).await.unwrap(); + let packet = client_to_handler_r.recv().await.unwrap(); + assert!(matches!(packet, Packet::Unsubscribe(unsub) if unsub.topics[0].as_ref() == "test/topic" )); - // Unsubscribe from multiple topics specified as an array of string slices: - let topics = ["test/topic1", "test/topic2"]; - let _ = mqtt_client.unsubscribe(topics.as_slice()).await; + // Unsubscribe from multiple topics specified as an array of String: + let topics = &[String::from("test/topic1"), String::from("test/topic2")]; + client.unsubscribe(topics.as_slice()).await.unwrap(); + let packet = client_to_handler_r.recv().await.unwrap(); + assert!(matches!(packet, Packet::Unsubscribe(unsub) if unsub.topics[0].as_ref() == "test/topic1" && unsub.topics[1].as_ref() == "test/topic2" )); + + std::hint::black_box((client, client_to_handler_r, to_network_r)); + } + + + #[test] + fn test_unsubscribe_blocking() { + let (client, client_to_handler_r, to_network_r) = create_new_test_client(); + + // Unsubscribe from a single topic specified as a string: + let topic = "test/topic"; + client.unsubscribe_blocking(topic).unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Unsubscribe(unsub) if unsub.topics[0].as_ref() == "test/topic" )); // Unsubscribe from a single topic specified as a String: let topic = String::from("test/topic"); - let _ = mqtt_client.unsubscribe(topic).await; + client.unsubscribe_blocking(topic).unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Unsubscribe(unsub) if unsub.topics[0].as_ref() == "test/topic" )); + + // Unsubscribe from multiple topics specified as an array of String: + let topics = &[String::from("test/topic1"), String::from("test/topic2")]; + client.unsubscribe_blocking(topics.as_slice()).unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Unsubscribe(unsub) if unsub.topics[0].as_ref() == "test/topic1" && unsub.topics[1].as_ref() == "test/topic2" )); + + std::hint::black_box((client, client_to_handler_r, to_network_r)); + } + + + #[test] + fn test_unsubscribe_with_properties_blocking() { + let (client, client_to_handler_r, to_network_r) = create_new_test_client(); + + let properties = UnsubscribeProperties{ + user_properties: vec![("property".to_string(), "value".to_string())], + }; + + // Unsubscribe from a single topic specified as a string: + let topic = "test/topic"; + client.unsubscribe_with_properties_blocking(topic, properties.clone()).unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Unsubscribe(unsub) if unsub.topics[0].as_ref() == "test/topic" )); + + // Unsubscribe from multiple topics specified as an array of string slices: + let topics = ["test/topic1", "test/topic2"]; + client.unsubscribe_with_properties_blocking(topics.as_slice(), properties.clone()).unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Unsubscribe(unsub) if unsub.topics[0].as_ref() == "test/topic1" && unsub.topics[1].as_ref() == "test/topic2" )); // Unsubscribe from multiple topics specified as a Vec: let topics = vec![String::from("test/topic1"), String::from("test/topic2")]; - let _ = mqtt_client.unsubscribe(topics).await; + client.unsubscribe_with_properties_blocking(topics, properties.clone()).unwrap(); + let packet = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(packet, Packet::Unsubscribe(unsub) if unsub.topics[0].as_ref() == "test/topic1" && unsub.topics[1].as_ref() == "test/topic2" )); - // Unsubscribe from multiple topics specified as an array of String: - let topics = &[String::from("test/topic1"), String::from("test/topic2")]; - let _ = mqtt_client.unsubscribe(topics.as_slice()).await; + std::hint::black_box((client, client_to_handler_r, to_network_r)); } #[tokio::test] @@ -1040,6 +1090,71 @@ mod tests { assert_eq!(res.unwrap_err(), ClientError::ValidationError(PacketValidationError::TopicSize(65538))); } + + #[tokio::test] + async fn publish_with_properties() { + let (client, client_to_handler_r, to_network_r) = create_new_test_client(); + + let properties = crate::packets::PublishProperties{ + response_topic: Some("response/topic".into()), + correlation_data: Some("correlation_other_data".into()), + ..Default::default() + }; + + for (id, qos) in [QoS::AtMostOnce, QoS::AtLeastOnce, QoS::ExactlyOnce].iter().enumerate() { + let res = client.publish_with_properties("way".repeat(21845).to_string(), *qos, false, "hello", properties.clone()).await; + + assert!(res.is_ok()); + let packet = client_to_handler_r.recv().await.unwrap(); + + let publ = Publish{ + dup: false, + qos: *qos, + retain: false, + topic: "way".repeat(21845).into(), + packet_identifier: if *qos == QoS::AtMostOnce { None } else { Some(id as u16) }, + publish_properties: properties.clone(), + payload: "hello".into(), + }; + + assert_eq!(Packet::Publish(publ), packet); + } + + std::hint::black_box((client, client_to_handler_r, to_network_r)); + } + + + #[tokio::test] + async fn publish_with_just_right_topic_len_properties() { + let (client, _client_to_handler_r, _) = create_new_test_client(); + + let properties = crate::packets::PublishProperties{ + response_topic: Some("response/topic".into()), + correlation_data: Some("correlation_data".into()), + ..Default::default() + }; + + let res = client.publish_with_properties("way".repeat(21845).to_string(), QoS::ExactlyOnce, false, "hello", properties).await; + + assert!(res.is_ok()); + } + + #[tokio::test] + async fn publish_with_too_long_topic_properties() { + let (client, _client_to_handler_r, _) = create_new_test_client(); + + let properties = crate::packets::PublishProperties{ + response_topic: Some("response/topic".into()), + correlation_data: Some("correlation_data".into()), + ..Default::default() + }; + + let res = client.publish_with_properties("way".repeat(21846).to_string(), QoS::ExactlyOnce, false, "hello", properties).await; + + assert!(res.is_err()); + assert_eq!(res.unwrap_err(), ClientError::ValidationError(PacketValidationError::TopicSize(65538))); + } + #[tokio::test] async fn subscribe_with_too_long_topic() { let (client, _client_to_handler_r, _) = create_new_test_client(); @@ -1103,33 +1218,21 @@ mod tests { let disconnect = client_to_handler_r.recv().await.unwrap(); assert_eq!(PacketType::Disconnect, disconnect.packet_type()); - if let Packet::Disconnect(res) = disconnect { - assert_eq!(DisconnectReasonCode::NormalDisconnection, res.reason_code); - assert_eq!(DisconnectProperties::default(), res.properties); - } else { - // To make sure we did the if branch - unreachable!(); - } + assert!(matches!(disconnect, Packet::Disconnect(res) if res.properties == DisconnectProperties::default() && DisconnectReasonCode::KeepAliveTimeout == res.reason_code)); } #[tokio::test] - async fn disconnect_with_properties_test() { + async fn test_disconnect_with_properties() { let (client, client_to_handler_r, _) = create_new_test_client(); client.disconnect_with_properties(DisconnectReasonCode::KeepAliveTimeout, Default::default()).await.unwrap(); let disconnect = client_to_handler_r.recv().await.unwrap(); assert_eq!(PacketType::Disconnect, disconnect.packet_type()); - if let Packet::Disconnect(res) = disconnect { - assert_eq!(DisconnectReasonCode::KeepAliveTimeout, res.reason_code); - assert_eq!(DisconnectProperties::default(), res.properties); - } else { - // To make sure we did the if branch - unreachable!(); - } + assert!(matches!(disconnect, Packet::Disconnect(res) if res.properties == DisconnectProperties::default() && DisconnectReasonCode::KeepAliveTimeout == res.reason_code)); } #[tokio::test] - async fn disconnect_with_properties_test2() { + async fn test_disconnect_with_properties_2() { let (client, client_to_handler_r, _) = create_new_test_client(); let properties = DisconnectProperties { reason_string: Some("TestString".into()), @@ -1140,12 +1243,37 @@ mod tests { let disconnect = client_to_handler_r.recv().await.unwrap(); assert_eq!(PacketType::Disconnect, disconnect.packet_type()); - if let Packet::Disconnect(res) = disconnect { - assert_eq!(DisconnectReasonCode::KeepAliveTimeout, res.reason_code); - assert_eq!(properties, res.properties); - } else { - // To make sure we did the if branch - unreachable!(); - } + assert!(matches!(disconnect, Packet::Disconnect(res) if properties == res.properties && DisconnectReasonCode::KeepAliveTimeout == res.reason_code)); + } + + + #[test] + fn test_disconnect_blocking() { + let (client, client_to_handler_r, _) = create_new_test_client(); + + let result = client.disconnect_blocking(); + + // Check that the function returns Ok + assert!(result.is_ok()); + + // Check that the last message sent was a Disconnect + let last_message = client_to_handler_r.recv_blocking().unwrap(); + assert!(matches!(last_message, Packet::Disconnect(_))); } + + #[test] + fn test_disconnect_blocking_with_properties() { + let (client, client_to_handler_r, _) = create_new_test_client(); + let properties = DisconnectProperties { + reason_string: Some("TestString".into()), + ..Default::default() + }; + + client.disconnect_with_properties_blocking(DisconnectReasonCode::KeepAliveTimeout, properties.clone()).unwrap(); + let disconnect = client_to_handler_r.recv_blocking().unwrap(); + assert_eq!(PacketType::Disconnect, disconnect.packet_type()); + + assert!(matches!(disconnect, Packet::Disconnect(res) if properties == res.properties && DisconnectReasonCode::KeepAliveTimeout == res.reason_code)); + } + } diff --git a/mqrstt/src/packets/disconnect.rs b/mqrstt/src/packets/disconnect.rs index 79f6877..d2307cb 100644 --- a/mqrstt/src/packets/disconnect.rs +++ b/mqrstt/src/packets/disconnect.rs @@ -8,21 +8,12 @@ use super::{ variable_integer_len, write_variable_integer, PacketType, PropertyType, }; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq)] pub struct Disconnect { pub reason_code: DisconnectReasonCode, pub properties: DisconnectProperties, } -impl Default for Disconnect { - fn default() -> Self { - Self { - reason_code: DisconnectReasonCode::NormalDisconnection, - properties: Default::default(), - } - } -} - impl VariableHeaderRead for Disconnect { fn read(_: u8, remaining_length: usize, mut buf: bytes::Bytes) -> Result { let reason_code; @@ -118,16 +109,20 @@ impl MqttWrite for DisconnectProperties { write_variable_integer(buf, self.wire_len())?; if let Some(session_expiry_interval) = self.session_expiry_interval { + PropertyType::SessionExpiryInterval.write(buf)?; buf.put_u32(session_expiry_interval); } if let Some(reason_string) = &self.reason_string { + PropertyType::ReasonString.write(buf)?; reason_string.write(buf)?; } for (key, val) in self.user_properties.iter() { + PropertyType::UserProperty.write(buf)?; key.write(buf)?; val.write(buf)?; } if let Some(server_refrence) = &self.server_reference { + PropertyType::ServerReference.write(buf)?; server_refrence.write(buf)?; } Ok(()) @@ -138,17 +133,94 @@ impl WireLength for DisconnectProperties { fn wire_len(&self) -> usize { let mut len = 0; if self.session_expiry_interval.is_some() { - len += 4; + len += 4 + 1; } if let Some(reason_string) = &self.reason_string { - len += reason_string.wire_len(); - } - for (key, val) in self.user_properties.iter() { - len += key.wire_len() + val.wire_len(); + len += reason_string.wire_len() + 1; } + len += self.user_properties.iter().fold(0, |acc, (k, v)| acc + k.wire_len() + v.wire_len() + 1); if let Some(server_refrence) = &self.server_reference { - len += server_refrence.wire_len(); + len += server_refrence.wire_len() + 1; } len } } + +#[cfg(test)] +mod tests { + use super::*; + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_write_and_read_disconnect() { + let mut buf = bytes::BytesMut::new(); + let packet = Disconnect { + properties: DisconnectProperties { + session_expiry_interval: Some(123), + reason_string: Some(Box::from("Some reason")), + user_properties: vec![ + (Box::from("key1"), Box::from("value1")), + (Box::from("key2"), Box::from("value2")), + ], + server_reference: Some(Box::from("Server reference")), + }, + reason_code: DisconnectReasonCode::NormalDisconnection, + }; + + packet.write(&mut buf).unwrap(); + + let read_packet = Disconnect::read(0, buf.len(), buf.into()).unwrap(); + + assert_eq!(read_packet.properties.session_expiry_interval, Some(123)); + assert_eq!(read_packet.properties.reason_string, Some(Box::from("Some reason"))); + assert_eq!( + read_packet.properties.user_properties, + vec![ + (Box::from("key1"), Box::from("value1")), + (Box::from("key2"), Box::from("value2")), + ] + ); + assert_eq!( + read_packet.properties.server_reference, + Some(Box::from("Server reference")) + ); + } +} + + + #[test] + fn test_write_and_read_disconnect_properties() { + let mut buf = bytes::BytesMut::new(); + let properties = DisconnectProperties { + session_expiry_interval: Some(123), + reason_string: Some(Box::from("Some reason")), + user_properties: vec![ + (Box::from("key1"), Box::from("value1")), + (Box::from("key2"), Box::from("value2")), + ], + server_reference: Some(Box::from("Server reference")), + }; + + properties.write(&mut buf).unwrap(); + + let read_properties = DisconnectProperties::read(&mut buf.into()).unwrap(); + + assert_eq!(read_properties.session_expiry_interval, Some(123)); + assert_eq!(read_properties.reason_string, Some(Box::from("Some reason"))); + assert_eq!( + read_properties.user_properties, + vec![ + (Box::from("key1"), Box::from("value1")), + (Box::from("key2"), Box::from("value2")), + ] + ); + assert_eq!( + read_properties.server_reference, + Some(Box::from("Server reference")) + ); + } +} \ No newline at end of file diff --git a/mqrstt/src/packets/reason_codes.rs b/mqrstt/src/packets/reason_codes.rs index 3f7f162..a940562 100644 --- a/mqrstt/src/packets/reason_codes.rs +++ b/mqrstt/src/packets/reason_codes.rs @@ -1,3 +1,5 @@ +use std::default; + use bytes::{Buf, BufMut}; use super::error::DeserializeError; @@ -134,8 +136,9 @@ impl MqttWrite for AuthReasonCode { } } -#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] +#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash)] pub enum DisconnectReasonCode { + #[default] NormalDisconnection, DisconnectWithWillMessage, UnspecifiedError,