summaryrefslogtreecommitdiffstats
path: root/mqtt-tester/src
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2023-01-19 08:22:37 +0000
committerGitHub <noreply@github.com>2023-01-19 08:22:37 +0000
commitb88efe118362e35bc75ae1d9782804899b6fd586 (patch)
tree785e8ddf6ed4ade79bf19361536e774ff9f0aefc /mqtt-tester/src
parent8732e5e0f14fa65ad9e330ee3c6d69e37157b8ec (diff)
parentff71a051fb621eb6a906f82a148fa1f943d955d4 (diff)
Merge #143
143: Reimpl behaviour tests r=TheNeikos a=matthiasbeyer This is extracted from #133 for making that PR smaller. These patches reimplement the tests as behaviour tests. Co-authored-by: Matthias Beyer <mail@beyermatthias.de>
Diffstat (limited to 'mqtt-tester/src')
-rw-r--r--mqtt-tester/src/behaviour/connack_flags_are_set_as_reserved.rs53
-rw-r--r--mqtt-tester/src/behaviour/first_packet_from_client_is_connect.rs63
-rw-r--r--mqtt-tester/src/behaviour/invalid_first_packet_is_rejected.rs59
-rw-r--r--mqtt-tester/src/behaviour/invalid_utf8_is_rejected.rs68
-rw-r--r--mqtt-tester/src/behaviour/mod.rs18
-rw-r--r--mqtt-tester/src/behaviour/publish_qos_2_is_acked.rs71
-rw-r--r--mqtt-tester/src/behaviour/publish_qos_zero_with_ident_fails.rs70
-rw-r--r--mqtt-tester/src/behaviour/receiving_server_packet.rs68
-rw-r--r--mqtt-tester/src/behaviour/utf8_with_nullchar_is_rejected.rs76
-rw-r--r--mqtt-tester/src/behaviour/wait_for_connect.rs21
-rw-r--r--mqtt-tester/src/behaviour_test.rs13
-rw-r--r--mqtt-tester/src/client_report.rs438
-rw-r--r--mqtt-tester/src/command.rs49
13 files changed, 639 insertions, 428 deletions
diff --git a/mqtt-tester/src/behaviour/connack_flags_are_set_as_reserved.rs b/mqtt-tester/src/behaviour/connack_flags_are_set_as_reserved.rs
new file mode 100644
index 0000000..49012c1
--- /dev/null
+++ b/mqtt-tester/src/behaviour/connack_flags_are_set_as_reserved.rs
@@ -0,0 +1,53 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use crate::{
+ behaviour_test::BehaviourTest,
+ command::{Input, Output},
+ executable::ClientExecutableCommand,
+ report::ReportResult,
+};
+
+pub struct ConnackFlagsAreSetAsReserved;
+
+#[async_trait::async_trait]
+impl BehaviourTest for ConnackFlagsAreSetAsReserved {
+ fn commands(&self) -> Vec<Box<dyn ClientExecutableCommand>> {
+ vec![]
+ }
+
+ async fn execute(&self, mut input: Input, _output: Output) -> Result<(), miette::Error> {
+ input
+ .send(&[
+ 0b0010_0000 | 0b0000_1000, // CONNACK + garbage
+ 0b0000_0010, // Remaining length
+ 0b0000_0000, // No session present
+ 0b0000_0000, // Connection accepted
+ ])
+ .await?;
+ Ok(())
+ }
+
+ fn report_name(&self) -> &str {
+ "Flag-Bit is set to 1 where it should be 0"
+ }
+
+ fn report_desc(&self) -> &str {
+ "CONNACK flag bits are marked as Reserved and must be set accordingly to spec"
+ }
+
+ fn report_normative(&self) -> &str {
+ "[MQTT-2.2.2-1]"
+ }
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Failure
+ } else {
+ ReportResult::Success
+ }
+ }
+}
diff --git a/mqtt-tester/src/behaviour/first_packet_from_client_is_connect.rs b/mqtt-tester/src/behaviour/first_packet_from_client_is_connect.rs
new file mode 100644
index 0000000..17525b6
--- /dev/null
+++ b/mqtt-tester/src/behaviour/first_packet_from_client_is_connect.rs
@@ -0,0 +1,63 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use mqtt_format::v3::packet::MPacket;
+
+use crate::{
+ behaviour_test::BehaviourTest,
+ command::{Input, Output},
+ executable::ClientExecutableCommand,
+ report::ReportResult,
+};
+
+pub struct FirstPacketFromClientIsConnect;
+
+#[async_trait::async_trait]
+impl BehaviourTest for FirstPacketFromClientIsConnect {
+ fn commands(&self) -> Vec<Box<dyn ClientExecutableCommand>> {
+ vec![]
+ }
+
+ async fn execute(&self, _input: Input, mut output: Output) -> Result<(), miette::Error> {
+ output
+ .wait_and_check(
+ &(|bytes: &[u8]| -> bool {
+ let packet = match nom::combinator::all_consuming(
+ mqtt_format::v3::packet::mpacket,
+ )(bytes)
+ {
+ Ok((_, packet)) => packet,
+ Err(_e) => return false,
+ };
+
+ std::matches!(packet, MPacket::Connect { .. })
+ }),
+ )
+ .await?;
+
+ Ok(())
+ }
+
+ fn report_name(&self) -> &str {
+ "First packet send by client must be CONNECT"
+ }
+
+ fn report_desc(&self) -> &str {
+ "After a Network Connection is established by a Client to a Server, the first Packet sent from the Client to the Server MUST be a CONNECT Packet."
+ }
+
+ fn report_normative(&self) -> &str {
+ "[MQTT-3.1.0-1]"
+ }
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Failure
+ } else {
+ ReportResult::Success
+ }
+ }
+}
diff --git a/mqtt-tester/src/behaviour/invalid_first_packet_is_rejected.rs b/mqtt-tester/src/behaviour/invalid_first_packet_is_rejected.rs
new file mode 100644
index 0000000..a4c95ad
--- /dev/null
+++ b/mqtt-tester/src/behaviour/invalid_first_packet_is_rejected.rs
@@ -0,0 +1,59 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use mqtt_format::v3::{packet::MConnect, strings::MString};
+
+use crate::{
+ behaviour_test::BehaviourTest,
+ command::{Input, Output},
+ executable::ClientExecutableCommand,
+ report::ReportResult,
+};
+
+pub struct InvalidFirstPacketIsRejected;
+
+#[async_trait::async_trait]
+impl BehaviourTest for InvalidFirstPacketIsRejected {
+ fn commands(&self) -> Vec<Box<dyn ClientExecutableCommand>> {
+ vec![]
+ }
+
+ async fn execute(&self, mut input: Input, _output: Output) -> Result<(), miette::Error> {
+ input
+ .send_packet(MConnect {
+ protocol_name: MString { value: "foo" },
+ protocol_level: 0,
+ clean_session: true,
+ will: None,
+ username: None,
+ password: None,
+ keep_alive: 0,
+ client_id: MString { value: "client" },
+ })
+ .await?;
+ Ok(())
+ }
+
+ fn report_name(&self) -> &str {
+ "Check if invalid first packet is rejected"
+ }
+
+ fn report_desc(&self) -> &str {
+ "The first packet from the server must be a ConnAck. Any other packet is invalid and the client should close the connection"
+ }
+
+ fn report_normative(&self) -> &str {
+ "[MQTT-3.2.0-1]"
+ }
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Failure
+ } else {
+ ReportResult::Success
+ }
+ }
+}
diff --git a/mqtt-tester/src/behaviour/invalid_utf8_is_rejected.rs b/mqtt-tester/src/behaviour/invalid_utf8_is_rejected.rs
new file mode 100644
index 0000000..efe7a6d
--- /dev/null
+++ b/mqtt-tester/src/behaviour/invalid_utf8_is_rejected.rs
@@ -0,0 +1,68 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use mqtt_format::v3::{connect_return::MConnectReturnCode, packet::MConnack};
+
+use crate::{
+ behaviour_test::BehaviourTest,
+ command::{Input, Output},
+ executable::ClientExecutableCommand,
+ report::ReportResult,
+};
+
+pub struct InvalidUtf8IsRejected;
+
+#[async_trait::async_trait]
+impl BehaviourTest for InvalidUtf8IsRejected {
+ fn commands(&self) -> Vec<Box<dyn ClientExecutableCommand>> {
+ vec![]
+ }
+
+ async fn execute(&self, mut input: Input, _output: Output) -> Result<(), miette::Error> {
+ input
+ .send_packet(MConnack {
+ session_present: false,
+ connect_return_code: MConnectReturnCode::Accepted,
+ })
+ .await?;
+
+ input
+ .send(&[
+ 0b0011_0000, // PUBLISH packet, DUP = 0, QoS = 0, Retain = 0
+ 0b0000_0111, // Length
+ // Now the variable header
+ 0b0000_0000,
+ 0b0000_0010,
+ 0x61,
+ 0xC1, // An invalid UTF-8 byte
+ 0b0000_0000, // Packet identifier
+ 0b0000_0001,
+ 0x1, // Payload
+ ])
+ .await?;
+ Ok(())
+ }
+
+ fn report_name(&self) -> &str {
+ "Check if invalid UTF-8 is rejected"
+ }
+
+ fn report_desc(&self) -> &str {
+ "Invalid UTF-8 is not allowed per the MQTT spec. Any receiver should immediately close the connection upon receiving such a packet."
+ }
+
+ fn report_normative(&self) -> &str {
+ "[MQTT-1.5.3-1, MQTT-1.5.3-2]"
+ }
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Failure
+ } else {
+ ReportResult::Success
+ }
+ }
+}
diff --git a/mqtt-tester/src/behaviour/mod.rs b/mqtt-tester/src/behaviour/mod.rs
index 48f9c92..0309b50 100644
--- a/mqtt-tester/src/behaviour/mod.rs
+++ b/mqtt-tester/src/behaviour/mod.rs
@@ -4,4 +4,22 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
+pub mod connack_flags_are_set_as_reserved;
+pub mod first_packet_from_client_is_connect;
+pub mod invalid_first_packet_is_rejected;
+pub mod invalid_utf8_is_rejected;
+pub mod publish_qos_2_is_acked;
+pub mod publish_qos_zero_with_ident_fails;
+pub mod receiving_server_packet;
+pub mod utf8_with_nullchar_is_rejected;
pub mod wait_for_connect;
+
+pub use self::connack_flags_are_set_as_reserved::ConnackFlagsAreSetAsReserved;
+pub use self::first_packet_from_client_is_connect::FirstPacketFromClientIsConnect;
+pub use self::invalid_first_packet_is_rejected::InvalidFirstPacketIsRejected;
+pub use self::invalid_utf8_is_rejected::InvalidUtf8IsRejected;
+pub use self::publish_qos_2_is_acked::PublishQos2IsAcked;
+pub use self::publish_qos_zero_with_ident_fails::PublishQosZeroWithIdentFails;
+pub use self::receiving_server_packet::ReceivingServerPacket;
+pub use self::utf8_with_nullchar_is_rejected::Utf8WithNullcharIsRejected;
+pub use self::wait_for_connect::WaitForConnect;
diff --git a/mqtt-tester/src/behaviour/publish_qos_2_is_acked.rs b/mqtt-tester/src/behaviour/publish_qos_2_is_acked.rs
new file mode 100644
index 0000000..95dfeae
--- /dev/null
+++ b/mqtt-tester/src/behaviour/publish_qos_2_is_acked.rs
@@ -0,0 +1,71 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use mqtt_format::v3::{
+ connect_return::MConnectReturnCode,
+ identifier::MPacketIdentifier,
+ packet::{MConnack, MPublish},
+ qos::MQualityOfService,
+ strings::MString,
+};
+
+use crate::{
+ behaviour_test::BehaviourTest,
+ command::{Input, Output},
+ executable::ClientExecutableCommand,
+ report::ReportResult,
+};
+
+pub struct PublishQos2IsAcked;
+
+#[async_trait::async_trait]
+impl BehaviourTest for PublishQos2IsAcked {
+ fn commands(&self) -> Vec<Box<dyn ClientExecutableCommand>> {
+ vec![]
+ }
+
+ async fn execute(&self, mut input: Input, _output: Output) -> Result<(), miette::Error> {
+ input
+ .send_packet(MConnack {
+ session_present: false,
+ connect_return_code: MConnectReturnCode::Accepted,
+ })
+ .await?;
+
+ input
+ .send_packet(MPublish {
+ dup: false,
+ qos: MQualityOfService::AtLeastOnce, // QoS 2
+ retain: false,
+ topic_name: MString { value: "a" },
+ id: Some(MPacketIdentifier(1)),
+ payload: &[0x00],
+ })
+ .await?;
+
+ Ok(())
+ }
+
+ fn report_name(&self) -> &str {
+ "A PUBLISH packet is replied to with Puback with the same id"
+ }
+
+ fn report_desc(&self) -> &str {
+ "A PUBACK, PUBREC or PUBREL Packet MUST contain the same Packet Identifier as the PUBLISH Packet that was originally sent."
+ }
+
+ fn report_normative(&self) -> &str {
+ "[MQTT-2.3.1-6]"
+ }
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Failure
+ } else {
+ ReportResult::Success
+ }
+ }
+}
diff --git a/mqtt-tester/src/behaviour/publish_qos_zero_with_ident_fails.rs b/mqtt-tester/src/behaviour/publish_qos_zero_with_ident_fails.rs
new file mode 100644
index 0000000..fe5544d
--- /dev/null
+++ b/mqtt-tester/src/behaviour/publish_qos_zero_with_ident_fails.rs
@@ -0,0 +1,70 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use mqtt_format::v3::{
+ connect_return::MConnectReturnCode,
+ identifier::MPacketIdentifier,
+ packet::{MConnack, MPublish},
+ qos::MQualityOfService,
+ strings::MString,
+};
+
+use crate::{
+ behaviour_test::BehaviourTest,
+ command::{Input, Output},
+ executable::ClientExecutableCommand,
+ report::ReportResult,
+};
+
+pub struct PublishQosZeroWithIdentFails;
+
+#[async_trait::async_trait]
+impl BehaviourTest for PublishQosZeroWithIdentFails {
+ fn commands(&self) -> Vec<Box<dyn ClientExecutableCommand>> {
+ vec![]
+ }
+
+ async fn execute(&self, mut input: Input, _output: Output) -> Result<(), miette::Error> {
+ input
+ .send_packet(MConnack {
+ session_present: false,
+ connect_return_code: MConnectReturnCode::Accepted,
+ })
+ .await?;
+
+ input
+ .send_packet(MPublish {
+ dup: false,
+ qos: MQualityOfService::AtMostOnce, // QoS 0
+ retain: false,
+ topic_name: MString { value: "a" },
+ id: Some(MPacketIdentifier(1)),
+ payload: &[0x00],
+ })
+ .await?;
+ Ok(())
+ }
+
+ fn report_name(&self) -> &str {
+ "A PUBLISH packet with QoS zero must not contain a packet identifier"
+ }
+
+ fn report_desc(&self) -> &str {
+ "A PUBLISH Packet MUST NOT contain a Packet Identifier if its QoS value is set to 0."
+ }
+
+ fn report_normative(&self) -> &str {
+ "[MQTT-2.3.1-5]"
+ }
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Failure
+ } else {
+ ReportResult::Success
+ }
+ }
+}
diff --git a/mqtt-tester/src/behaviour/receiving_server_packet.rs b/mqtt-tester/src/behaviour/receiving_server_packet.rs
new file mode 100644
index 0000000..f88888e
--- /dev/null
+++ b/mqtt-tester/src/behaviour/receiving_server_packet.rs
@@ -0,0 +1,68 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use mqtt_format::v3::{
+ connect_return::MConnectReturnCode,
+ identifier::MPacketIdentifier,
+ packet::{MConnack, MSubscribe},
+ subscription_request::MSubscriptionRequests,
+};
+
+use crate::{
+ behaviour_test::BehaviourTest,
+ command::{Input, Output},
+ executable::ClientExecutableCommand,
+ report::ReportResult,
+};
+
+pub struct ReceivingServerPacket;
+
+#[async_trait::async_trait]
+impl BehaviourTest for ReceivingServerPacket {
+ fn commands(&self) -> Vec<Box<dyn ClientExecutableCommand>> {
+ vec![]
+ }
+
+ async fn execute(&self, mut input: Input, _output: Output) -> Result<(), miette::Error> {
+ input
+ .send_packet(MConnack {
+ session_present: false,
+ connect_return_code: MConnectReturnCode::Accepted,
+ })
+ .await?;
+
+ input
+ .send_packet(MSubscribe {
+ id: MPacketIdentifier(1),
+ subscriptions: MSubscriptionRequests {
+ count: 1,
+ data: b"a/b",
+ },
+ })
+ .await?;
+ Ok(())
+ }
+
+ fn report_name(&self) -> &str {
+ "Check if invalid packets are rejected"
+ }
+
+ fn report_desc(&self) -> &str {
+ "Unexpected packets are a protocol error and the client MUST close the connection."
+ }
+
+ fn report_normative(&self) -> &str {
+ "[MQTT-4.8.0-1]"
+ }
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Failure
+ } else {
+ ReportResult::Success
+ }
+ }
+}
diff --git a/mqtt-tester/src/behaviour/utf8_with_nullchar_is_rejected.rs b/mqtt-tester/src/behaviour/utf8_with_nullchar_is_rejected.rs
new file mode 100644
index 0000000..32283b6
--- /dev/null
+++ b/mqtt-tester/src/behaviour/utf8_with_nullchar_is_rejected.rs
@@ -0,0 +1,76 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use mqtt_format::v3::{
+ connect_return::MConnectReturnCode, header::MPacketKind, packet::MConnack,
+ qos::MQualityOfService,
+};
+
+use crate::{
+ behaviour_test::BehaviourTest,
+ command::{Input, Output},
+ executable::ClientExecutableCommand,
+ report::ReportResult,
+};
+
+pub struct Utf8WithNullcharIsRejected;
+
+#[async_trait::async_trait]
+impl BehaviourTest for Utf8WithNullcharIsRejected {
+ fn commands(&self) -> Vec<Box<dyn ClientExecutableCommand>> {
+ vec![]
+ }
+
+ async fn execute(&self, mut input: Input, _output: Output) -> Result<(), miette::Error> {
+ input
+ .send_packet(MConnack {
+ session_present: false,
+ connect_return_code: MConnectReturnCode::Accepted,
+ })
+ .await?;
+
+ input
+ .send(&[
+ (MPacketKind::Publish {
+ dup: false,
+ qos: MQualityOfService::AtMostOnce,
+ retain: false,
+ })
+ .to_byte(),
+ 0b0000_0111, // Length
+ // Now the variable header
+ 0b0000_0000,
+ 0b0000_0010,
+ 0x61,
+ 0x00, // Zero byte
+ 0b0000_0000, // Packet identifier
+ 0b0000_0001,
+ 0x1, // Payload
+ ])
+ .await?;
+ Ok(())
+ }
+
+ fn report_name(&self) -> &str {
+ "Check if connection gets closed if UTF-8 string contains nullchar"
+ }
+
+ fn report_desc(&self) -> &str {
+ "The A UTF-8 encoded string MUST NOT include an encoding of the null character U+0000"
+ }
+
+ fn report_normative(&self) -> &str {
+ "[MQTT-1.5.3-2]"
+ }
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Success
+ } else {
+ ReportResult::Failure
+ }
+ }
+}
diff --git a/mqtt-tester/src/behaviour/wait_for_connect.rs b/mqtt-tester/src/behaviour/wait_for_connect.rs
index d61fe85..561acca 100644
--- a/mqtt-tester/src/behaviour/wait_for_connect.rs
+++ b/mqtt-tester/src/behaviour/wait_for_connect.rs
@@ -8,6 +8,7 @@ use crate::{
behaviour_test::BehaviourTest,
command::{Input, Output},
executable::ClientExecutableCommand,
+ report::ReportResult,
};
pub struct WaitForConnect;
@@ -40,6 +41,26 @@ impl BehaviourTest for WaitForConnect {
)
.await
}
+
+ fn report_name(&self) -> &str {
+ "Wait for client to connect"
+ }
+
+ fn report_desc(&self) -> &str {
+ "A client should send a CONNECT packet to connect to the server"
+ }
+
+ fn report_normative(&self) -> &str {
+ "none"
+ }
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Success
+ } else {
+ ReportResult::Failure
+ }
+ }
}
fn find_connect_flags(bytes: &[u8]) -> Option<u8> {
diff --git a/mqtt-tester/src/behaviour_test.rs b/mqtt-tester/src/behaviour_test.rs
index a601c5c..fd334df 100644
--- a/mqtt-tester/src/behaviour_test.rs
+++ b/mqtt-tester/src/behaviour_test.rs
@@ -7,6 +7,7 @@
use crate::{
command::{Input, Output},
executable::ClientExecutableCommand,
+ report::ReportResult,
};
#[async_trait::async_trait]
@@ -14,4 +15,16 @@ pub trait BehaviourTest {
fn commands(&self) -> Vec<Box<dyn ClientExecutableCommand>>;
async fn execute(&self, mut input: Input, mut output: Output) -> Result<(), miette::Error>;
+
+ fn report_name(&self) -> &str;
+ fn report_desc(&self) -> &str;
+ fn report_normative(&self) -> &str;
+
+ fn translate_client_exit_code(&self, success: bool) -> ReportResult {
+ if success {
+ ReportResult::Success
+ } else {
+ ReportResult::Failure
+ }
+ }
}
diff --git a/mqtt-tester/src/client_report.rs b/mqtt-tester/src/client_report.rs
index 34dfcc2..f02359d 100644
--- a/mqtt-tester/src/client_report.rs
+++ b/mqtt-tester/src/client_report.rs
@@ -6,21 +6,14 @@
use std::path::PathBuf;
use std::sync::Arc;
-use std::time::Duration;
use futures::FutureExt;
use miette::IntoDiagnostic;
-use mqtt_format::v3::connect_return::MConnectReturnCode;
-use mqtt_format::v3::header::MPacketKind;
-use mqtt_format::v3::identifier::MPacketIdentifier;
-use mqtt_format::v3::packet::{MConnack, MConnect, MPacket, MPuback, MPublish, MSubscribe};
+use mqtt_format::v3::packet::{MConnect, MPacket};
-use mqtt_format::v3::qos::MQualityOfService;
use mqtt_format::v3::strings::MString;
-use mqtt_format::v3::subscription_request::MSubscriptionRequests;
-use crate::behaviour::wait_for_connect::WaitForConnect;
use crate::behaviour_test::BehaviourTest;
use crate::executable::ClientExecutable;
use crate::invariant::no_username_means_no_password::NoUsernameMeansNoPassword;
@@ -36,14 +29,6 @@ pub async fn create_client_report(
let executable = ClientExecutable::new(client_exe_path);
let reports = vec![
- check_invalid_utf8_is_rejected(&executable).boxed_local(),
- check_receiving_server_packet(&executable).boxed_local(),
- check_invalid_first_packet_is_rejected(&executable).boxed_local(),
- check_utf8_with_nullchar_is_rejected(&executable).boxed_local(),
- check_connack_flags_are_set_as_reserved(&executable).boxed_local(),
- check_publish_qos_zero_with_ident_fails(&executable).boxed_local(),
- check_publish_qos_2_is_acked(&executable).boxed_local(),
- check_first_packet_from_client_is_connect(&executable).boxed_local(),
check_connect_packet_protocol_name(&executable).boxed_local(),
check_connect_packet_reserved_flag_zero(&executable).boxed_local(),
check_connect_flag_username_set_username_present(&executable).boxed_local(),
@@ -51,10 +36,21 @@ pub async fn create_client_report(
check_connect_flag_username_zero_means_password_zero(&executable).boxed_local(),
];
- let flows = vec![Box::new(WaitForConnect)];
+ let flows: Vec<Box<dyn BehaviourTest>> = vec![
+ Box::new(crate::behaviour::WaitForConnect),
+ Box::new(crate::behaviour::InvalidUtf8IsRejected),
+ Box::new(crate::behaviour::ReceivingServerPacket),
+ Box::new(crate::behaviour::InvalidFirstPacketIsRejected),
+ Box::new(crate::behaviour::Utf8WithNullcharIsRejected),
+ Box::new(crate::behaviour::ConnackFlagsAreSetAsReserved),
+ Box::new(crate::behaviour::PublishQosZeroWithIdentFails),
+ Box::new(crate::behaviour::PublishQos2IsAcked),
+ Box::new(crate::behaviour::FirstPacketFromClientIsConnect),
+ ];
let invariants: Vec<Arc<dyn PacketInvariant>> = vec![Arc::new(NoUsernameMeansNoPassword)];
+ let mut collected_reports = Vec::with_capacity(flows.len());
for flow in flows {
let commands = flow.commands();
@@ -65,16 +61,34 @@ pub async fn create_client_report(
output.with_invariants(invariants.iter().cloned());
- flow.execute(input, output).await?;
- client.wait_with_output().await.into_diagnostic()?;
+ let flow_result = flow.execute(input, output).await;
+ let client_output = client.wait_with_output().await.into_diagnostic()?;
+
+ collected_reports.push({
+ Report {
+ name: String::from(flow.report_name()),
+ description: String::from(flow.report_desc()),
+ normative_statement_number: String::from(flow.report_normative()),
+ result: match flow_result {
+ Ok(_) => flow.translate_client_exit_code(client_output.status.success()),
+ Err(_e) => ReportResult::Failure,
+ },
+ output: Some(client_output.stdout),
+ }
+ })
}
- futures::stream::iter(reports)
- .buffered(parallelism.get())
- .collect::<Vec<_>>()
- .await
- .into_iter()
- .collect::<Result<Vec<_>, _>>()
+ Ok({
+ futures::stream::iter(reports)
+ .buffered(parallelism.get())
+ .collect::<Vec<_>>()
+ .await
+ .into_iter()
+ .collect::<Result<Vec<_>, _>>()?
+ .into_iter()
+ .chain(collected_reports.into_iter())
+ .collect()
+ })
}
#[macro_export]
@@ -107,369 +121,35 @@ macro_rules! mk_report {
};
}
+#[macro_export]
macro_rules! wait_for_output {
($output:ident,
timeout_ms: $timeout_ms:literal,
out_success => $success:block,
out_failure => $failure:block
) => {{
- let (result, output) =
- match tokio::time::timeout(Duration::from_millis($timeout_ms), $output).await {
- Ok(Ok(out)) => (
- if out.status.success() {
- $success
- } else {
- $failure
- },
- Some(out.stderr),
- ),
- Ok(Err(_)) | Err(_) => (ReportResult::Failure, None),
- };
-
- (result, output)
- }};
-}
-
-async fn check_invalid_utf8_is_rejected(executable: &ClientExecutable) -> miette::Result<Report> {
- let (client, mut input, _output) = executable
- .call(&[])
- .map(crate::command::Command::new)?
- .spawn()?;
-
- input
- .send_packet(MConnack {
- session_present: false,
- connect_return_code: MConnectReturnCode::Accepted,
- })
- .await?;
+ #[allow(unused_imports)]
+ use futures::Future;
- input
- .send(&[
- 0b0011_0000, // PUBLISH packet, DUP = 0, QoS = 0, Retain = 0
- 0b0000_0111, // Length
- // Now the variable header
- 0b0000_0000,
- 0b0000_0010,
- 0x61,
- 0xC1, // An invalid UTF-8 byte
- 0b0000_0000, // Packet identifier
- 0b0000_0001,
- 0x1, // Payload
- ])
- .await?;
-
- let output = client.wait_with_output();
- let (result, output) = wait_for_output! {
- output,
- timeout_ms: 100,
- out_success => { ReportResult::Failure },
- out_failure => { ReportResult::Success }
- };
-
- Ok(mk_report! {
- name: "Check if invalid UTF-8 is rejected",
- desc: "Invalid UTF-8 is not allowed per the MQTT spec. Any receiver should immediately close the connection upon receiving such a packet.",
- normative: "[MQTT-1.5.3-1, MQTT-1.5.3-2]",
- result,
- output
- })
-}
-
-async fn check_receiving_server_packet(executable: &ClientExecutable) -> miette::Result<Report> {
- let (client, mut input, _output) = executable
- .call(&[])
- .map(crate::command::Command::new)?
- .spawn()?;
-
- input
- .send_packet(MConnack {
- session_present: false,
- connect_return_code: MConnectReturnCode::Accepted,
- })
- .await?;
-
- input
- .send_packet(MSubscribe {
- id: MPacketIdentifier(1),
- subscriptions: MSubscriptionRequests {
- count: 1,
- data: b"a/b",
- },
- })
- .await?;
-
- let output = client.wait_with_output();
- let (result, output) = wait_for_output! {
- output,
- timeout_ms: 100,
- out_success => { ReportResult::Failure },
- out_failure => { ReportResult::Success }
- };
-
- Ok(mk_report! {
- name: "Check if invalid packets are rejected",
- desc: "Unexpected packets are a protocol error and the client MUST close the connection.",
- normative: "[MQTT-4.8.0-1]",
- result,
- output
- })
-}
-
-async fn check_invalid_first_packet_is_rejected(
- executable: &ClientExecutable,
-) -> miette::Result<Report> {
- let (client, mut input, _output) = executable
- .call(&[])
- .map(crate::command::Command::new)?
- .spawn()?;
-
- input
- .send_packet(MConnect {
-