diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2023-01-19 08:22:37 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-19 08:22:37 +0000 |
commit | b88efe118362e35bc75ae1d9782804899b6fd586 (patch) | |
tree | 785e8ddf6ed4ade79bf19361536e774ff9f0aefc /mqtt-tester/src | |
parent | 8732e5e0f14fa65ad9e330ee3c6d69e37157b8ec (diff) | |
parent | ff71a051fb621eb6a906f82a148fa1f943d955d4 (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.rs | 53 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour/first_packet_from_client_is_connect.rs | 63 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour/invalid_first_packet_is_rejected.rs | 59 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour/invalid_utf8_is_rejected.rs | 68 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour/mod.rs | 18 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour/publish_qos_2_is_acked.rs | 71 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour/publish_qos_zero_with_ident_fails.rs | 70 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour/receiving_server_packet.rs | 68 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour/utf8_with_nullchar_is_rejected.rs | 76 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour/wait_for_connect.rs | 21 | ||||
-rw-r--r-- | mqtt-tester/src/behaviour_test.rs | 13 | ||||
-rw-r--r-- | mqtt-tester/src/client_report.rs | 438 | ||||
-rw-r--r-- | mqtt-tester/src/command.rs | 49 |
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 { - |