]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
mqtt: add unittests for nom7 parsers
authorHaleema Khan <hsadia538@gmail.com>
Wed, 28 Dec 2022 18:57:45 +0000 (23:57 +0500)
committerVictor Julien <vjulien@oisf.net>
Tue, 10 Jan 2023 10:21:10 +0000 (11:21 +0100)
Ticket: #5742

rust/src/mqtt/mqtt_property.rs
rust/src/mqtt/parser.rs

index 789d81ee1458b3904ffd9645abf4203e53b31dac..a716c4b6527397de205cae4f13393bfdd9d4c6ee 100644 (file)
@@ -24,7 +24,7 @@ use nom7::IResult;
 
 // TODO: It might be useful to also add detection on property presence and
 // content, e.g. mqtt.property: AUTHENTICATION_METHOD.
-#[derive(Debug)]
+#[derive(Debug, PartialEq, PartialOrd)]
 #[allow(non_camel_case_types)]
 pub enum MQTTProperty {
     UNKNOWN,
index 78ef4ba01e1f65c8d3bdb8144361c51c06d773c3..3d93f0a3b501cfbf11a39d77829b697d78ea5272 100644 (file)
@@ -1,4 +1,4 @@
-/* Copyright (C) 2020 Open Information Security Foundation
+/* Copyright (C) 2020-2022 Open Information Security Foundation
  *
  * You can copy, redistribute or modify this Program under the terms of
  * the GNU General Public License version 2 as published by the Free
@@ -742,4 +742,386 @@ mod tests {
     fn test_mqtt_parse_variable_integer_smallest_valid() {
         test_mqtt_parse_variable_check(&[0x0], 0);
     }
+
+    #[test]
+    fn test_parse_fixed_header() {
+        let buf = [
+            0x30, /* Header Flags: 0x30, Message Type: Publish Message, QoS Level: At most once delivery (Fire and Forget) */
+            0xb7, 0x97, 0x02, /* Msg Len: 35767 */
+            0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0xa0,
+        ];
+
+        let result = parse_fixed_header(&buf);
+        match result {
+            Ok((remainder, message)) => {
+                assert_eq!(message.message_type, MQTTTypeCode::PUBLISH);
+                assert_eq!(message.dup_flag, false);
+                assert_eq!(message.qos_level, 0);
+                assert_eq!(message.retain, false);
+                assert_eq!(message.remaining_length, 35767);
+                assert_eq!(remainder.len(), 17);
+            }
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    #[test]
+    fn test_parse_properties() {
+        let buf = [
+            0x03, 0x21, 0x00, 0x14, /* Properties */
+            0x00, 0xff, 0x00, 0xff, 0x00, 0xff, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+            0x00, 0x01, 0xa0,
+        ];
+
+        let result = parse_properties(&buf, true);
+        match result {
+            Ok((remainder, message)) => {
+                let res = message.unwrap();
+                assert_eq!(res[0], MQTTProperty::RECEIVE_MAXIMUM(20));
+                assert_eq!(remainder.len(), 17);
+            }
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+    #[test]
+    fn test_parse_connect() {
+        let buf = [
+            0x00, 0x04, /* Protocol Name Length: 4 */
+            0x4d, 0x51, 0x54, 0x54, /* Protocol Name: MQTT */
+            0x05, /* Version: MQTT v5.0 (5) */
+            0xc2, /*Connect Flags: 0xc2, User Name Flag, Password Flag, QoS Level: At most once delivery (Fire and Forget), Clean Session Flag */
+            0x00, 0x3c, /* Keep Alive: 60 */
+            0x03, 0x21, 0x00, 0x14, /* Properties */
+            0x00, 0x00, /* Client ID Length: 0 */
+            0x00, 0x04, /* User Name Length: 4 */
+            0x75, 0x73, 0x65, 0x72, /* User Name: user */
+            0x00, 0x04, /* Password Length: 4 */
+            0x71, 0x61, 0x71, 0x73, /* Password: pass */
+        ];
+
+        let result = parse_connect(&buf);
+        match result {
+            Ok((remainder, message)) => {
+                assert_eq!(message.protocol_string, "MQTT");
+                assert_eq!(message.protocol_version, 5);
+                assert_eq!(message.username_flag, true);
+                assert_eq!(message.password_flag, true);
+                assert_eq!(message.will_retain, false);
+                assert_eq!(message.will_qos, 0);
+                assert_eq!(message.will_flag, false);
+                assert_eq!(message.clean_session, true);
+                assert_eq!(message.keepalive, 60);
+                assert_eq!(remainder.len(), 0);
+            }
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    #[test]
+    fn test_parse_connack() {
+        let buf = [
+            0x00, /* Acknowledge Flags: 0x00 (0000 000. = Reserved: Not set )(.... ...0 = Session Present: Not set) */
+            0x00, /* Reason Code: Success (0) */
+            0x2f, /* Total Length: 47 */
+            0x22, /* ID: Topic Alias Maximum (0x22) */
+            0x00, 0x0a, /* Value: 10 */
+            0x12, /* ID: Assigned Client Identifier (0x12) */
+            0x00, 0x29, /* Length: 41 */
+            0x61, 0x75, 0x74, 0x6f, 0x2d, 0x31, 0x42, 0x34, 0x33, 0x45, 0x38, 0x30, 0x30, 0x2d,
+            0x30, 0x38, 0x45, 0x33, 0x2d, 0x33, 0x42, 0x41, 0x31, 0x2d, 0x32, 0x45, 0x39, 0x37,
+            0x2d, 0x45, 0x39, 0x41, 0x30, 0x42, 0x34, 0x30, 0x36, 0x34, 0x42, 0x46,
+            0x35, /* 41 byte Value: auto-1B43E800-08E3-3BA1-2E97-E9A0B4064BF5 */
+        ];
+        let client_identifier = "auto-1B43E800-08E3-3BA1-2E97-E9A0B4064BF5";
+
+        let result = parse_connack(5);
+        let input = result(&buf);
+        match input {
+            Ok((remainder, message)) => {
+                let props = message.properties.unwrap();
+                assert_eq!(props[0], MQTTProperty::TOPIC_ALIAS_MAXIMUM(10));
+                assert_eq!(
+                    props[1],
+                    MQTTProperty::ASSIGNED_CLIENT_IDENTIFIER(client_identifier.to_string())
+                );
+                assert_eq!(message.return_code, 0);
+                assert_eq!(message.session_present, false);
+                assert_eq!(remainder.len(), 0);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    #[test]
+    fn test_parse_publish() {
+        let buf = [
+            0x00, 06, /* Topic Length: 6 */
+            0x74, 0x6f, 0x70, 0x69, 0x63, 0x58, /* Topic: topicX */
+            0x00, 0x01, /* Message Identifier: 1 */
+            0x00, /* Properties 6 */
+            0x00, 0x61, 0x75, 0x74, 0x6f, 0x2d, 0x42, 0x34, 0x33, 0x45, 0x38, 0x30,
+        ];
+
+        let result = parse_publish(5, true);
+        let input = result(&buf);
+        match input {
+            Ok((remainder, message)) => {
+                let message_id = message.message_id.unwrap();
+                assert_eq!(message.topic, "topicX");
+                assert_eq!(message_id, 1);
+                assert_eq!(remainder.len(), 13);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    #[test]
+    fn test_parse_msgidonly_v3() {
+        let buf = [
+            0x00, 01, /* Message Identifier: 1 */
+            0x74, 0x6f, 0x70, 0x69, 0x63, 0x58, 0x00, 0x61, 0x75, 0x74, 0x6f, 0x2d, 0x42, 0x34,
+            0x33, 0x45, 0x38, 0x30,
+        ];
+
+        let result = parse_msgidonly(3);
+        let input = result(&buf);
+        match input {
+            Ok((remainder, message)) => {
+                assert_eq!(message.message_id, 1);
+                assert_eq!(remainder.len(), 18);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    #[test]
+    fn test_parse_msgidonly_v5() {
+        let buf = [
+            0x00, 01,   /* Message Identifier: 1 */
+            0x00, /* Reason Code: 0 */
+            0x00, /* Properties */
+            0x00, 0x61, 0x75, 0x74, 0x6f, 0x2d, 0x42, 0x34, 0x33, 0x45, 0x38, 0x30,
+        ];
+
+        let result = parse_msgidonly(5);
+        let input = result(&buf);
+        match input {
+            Ok((remainder, message)) => {
+                let reason_code = message.reason_code.unwrap();
+                assert_eq!(message.message_id, 1);
+                assert_eq!(reason_code, 0);
+                assert_eq!(remainder.len(), 12);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    #[test]
+    fn test_parse_subscribe() {
+        let buf = [
+            0x00, 0x01, /* Message Identifier: 1 */
+            0x00, /* Properties 6 */
+            0x00, 0x06, /* Topic Length: 6  */
+            0x74, 0x6f, 0x70, 0x69, 0x63, 0x58, /* Topic: topicX */
+            0x00, /*Subscription Options: 0x00, Retain Handling: Send msgs at subscription time, QoS: At most once delivery (Fire and Forget) */
+            0x00, 0x06, /* Topic Length: 6  */
+            0x74, 0x6f, 0x70, 0x69, 0x63, 0x59, /* Topic: topicY */
+            0x00, /*Subscription Options: 0x00, Retain Handling: Send msgs at subscription time, QoS: At most once delivery (Fire and Forget) */
+            0x00, 0x61, 0x75, 0x74, 0x6f, 0x2d, 0x42, 0x34, 0x33, 0x45, 0x38, 0x30,
+        ];
+
+        let result = parse_subscribe(5);
+        let input = result(&buf);
+        match input {
+            Ok((remainder, message)) => {
+                assert_eq!(message.topics[0].topic_name, "topicX");
+                assert_eq!(message.topics[1].topic_name, "topicY");
+                assert_eq!(message.topics[0].qos, 0);
+                assert_eq!(message.message_id, 1);
+                assert_eq!(remainder.len(), 12);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+    #[test]
+    fn test_parse_suback() {
+        let buf = [
+            0x00, 0x01, /* Message Identifier: 1 */
+            0x00, /* Properties 6 */
+            0x00, 0x00, /* Topic Length: 6  */
+        ];
+
+        let result = parse_suback(5);
+        let input = result(&buf);
+        match input {
+            Ok((remainder, message)) => {
+                assert_eq!(message.qoss[0], 0);
+                assert_eq!(message.message_id, 1);
+                assert_eq!(remainder.len(), 3);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+    #[test]
+    fn test_parse_unsubscribe() {
+        let buf = [
+            0x00, 0x01, /* Message Identifier: 1 */
+            0x00, /* Properties 6 */
+            0x00, 0x06, /* Topic Length: 6  */
+            0x74, 0x6f, 0x70, 0x69, 0x63, 0x58, /* Topic: topicX */
+            0x00, /*Subscription Options: 0x00, Retain Handling: Send msgs at subscription time, QoS: At most once delivery (Fire and Forget) */
+        ];
+
+        let result = parse_unsubscribe(5);
+        let input = result(&buf);
+        match input {
+            Ok((remainder, message)) => {
+                assert_eq!(message.topics[0], "topicX");
+                assert_eq!(message.message_id, 1);
+                assert_eq!(remainder.len(), 1);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    #[test]
+    fn test_parse_unsuback() {
+        let buf = [
+            0x00, 0x01, /* Message Identifier: 1 */
+            0x00, /* Properties 6 */
+            0x00, /* Reason Code */
+        ];
+
+        let result = parse_unsuback(5);
+        let input = result(&buf);
+        match input {
+            Ok((remainder, message)) => {
+                let reason_codes = message.reason_codes.unwrap();
+                assert_eq!(reason_codes[0], 0);
+                assert_eq!(message.message_id, 1);
+                assert_eq!(remainder.len(), 0);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    #[test]
+    fn test_parse_disconnect() {
+        let buf = [
+            0xe0, /* Reason: 0 */
+            0x00, /* Message Identifier: 1 */
+        ];
+
+        let result = parse_disconnect(0, 5);
+        let input = result(&buf);
+        match input {
+            Ok((remainder, message)) => {
+                let reason_code = message.reason_code.unwrap();
+                assert_eq!(reason_code, 0);
+                assert_eq!(remainder.len(), 2);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
+
+    #[test]
+    fn test_parse_message() {
+        let buf = [
+            0x10, /* Message Identifier: 1 */
+            0x2f, 0x00, 0x04, 0x4d, 0x51, 0x54, 0x54, 0x05,
+            0xc2, /* Connect Flags: 0xc2, User Name Flag, Password Flag, QoS Level: At most once delivery (Fire and Forget), Clean Session Flag */
+            0x00, 0x3c, 0x03, 0x21, 0x00, 0x14, /* Properties */
+            0x00, 0x13, 0x6d, 0x79, 0x76, 0x6f, 0x69, 0x63, 0x65, 0x69, 0x73, 0x6d, 0x79, 0x70,
+            0x61, 0x73, 0x73, 0x70, 0x6f, 0x72, 0x74, 0x00, 0x04, 0x75, 0x73, 0x65, 0x72, 0x00,
+            0x04, 0x70, 0x61, 0x73, 0x73,
+        ];
+
+        let result = parse_message(&buf, 5, 40);
+        match result {
+            Ok((remainder, message)) => {
+                assert_eq!(message.header.message_type, MQTTTypeCode::CONNECT);
+                assert_eq!(message.header.dup_flag, false);
+                assert_eq!(message.header.qos_level, 0);
+                assert_eq!(message.header.retain, false);
+                assert_eq!(remainder.len(), 49);
+            }
+
+            Err(Err::Incomplete(_)) => {
+                panic!("Result should not have been incomplete.");
+            }
+            Err(Err::Error(err)) | Err(Err::Failure(err)) => {
+                panic!("Result should not be an error: {:?}.", err);
+            }
+        }
+    }
 }