]> git.ipfire.org Git - thirdparty/suricata.git/commitdiff
modbus: move from C to rust
authorSimon Dugas <simon.dugas@cyber.gc.ca>
Thu, 11 Feb 2021 20:58:43 +0000 (15:58 -0500)
committerVictor Julien <victor@inliniac.net>
Tue, 4 May 2021 08:43:10 +0000 (10:43 +0200)
Adds a new rust modbus app layer parser and detection module.

Moves the C module to rust but leaves the test cases in place to
regression test the new rust module.

rust/Cargo.toml.in
rust/cbindgen.toml
rust/src/lib.rs
rust/src/modbus/detect.rs [new file with mode: 0644]
rust/src/modbus/mod.rs [new file with mode: 0644]
rust/src/modbus/modbus.rs [new file with mode: 0644]
src/app-layer-modbus.c
src/app-layer-modbus.h
src/detect-engine-modbus.c
src/detect-modbus.c
src/suricata.c

index 89681f478c1b1a4c851d20ca84075e276589d34a..234d5dc0261cb07880177b7612c610f73ad2c1e1 100644 (file)
@@ -33,6 +33,8 @@ widestring = "~0.4.3"
 flate2 = "~1.0.19"
 brotli = "~3.3.0"
 
+sawp-modbus = "~0.4.0"
+sawp = "~0.4.0"
 der-parser = "~4.0.2"
 kerberos-parser = "~0.5.0"
 ntp-parser = "~0.4.0"
@@ -45,6 +47,8 @@ sha2 = "~0.9.2"
 digest = "~0.9.0"
 sha-1 = "~0.9.2"
 md-5 = "~0.9.1"
+regex = "~1.4.2"
+lazy_static = "~1.4.0"
 
 [dev-dependencies]
 test-case = "~1.1.0"
index 71609d4dc14dfb9dd7042e12c6882f7b2bdf5e06..d3b33312e36bcba9cd0e56e629fe86865bc1f7c6 100644 (file)
@@ -75,6 +75,7 @@ include = [
     "AppLayerGetTxIterTuple",
     "RdpState",
     "SIPState",
+    "ModbusState",
     "CMark",
 ]
 
index 10ed302d470b69d0645947108bbfcd6362ed42a9..e563d8c9cd42952a6bf03b26d5c25323e49b3e2a 100644 (file)
@@ -66,6 +66,7 @@ pub mod ftp;
 pub mod smb;
 pub mod krb;
 pub mod dcerpc;
+pub mod modbus;
 
 pub mod ike;
 pub mod snmp;
diff --git a/rust/src/modbus/detect.rs b/rust/src/modbus/detect.rs
new file mode 100644 (file)
index 0000000..af402f1
--- /dev/null
@@ -0,0 +1,539 @@
+/* Copyright (C) 2021 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
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+use super::modbus::ModbusTransaction;
+use lazy_static::lazy_static;
+use regex::Regex;
+use sawp_modbus::{AccessType, CodeCategory, Data, Flags, FunctionCode, Message};
+use std::ffi::CStr;
+use std::ops::{Range, RangeInclusive};
+use std::os::raw::{c_char, c_void};
+use std::str::FromStr;
+
+lazy_static! {
+    static ref ACCESS_RE: Regex = Regex::new(
+        "^\\s*\"?\\s*access\\s*(read|write)\
+        \\s*(discretes|coils|input|holding)?\
+        (?:,\\s*address\\s+([<>]?\\d+)(?:<>(\\d+))?\
+        (?:,\\s*value\\s+([<>]?\\d+)(?:<>(\\d+))?)?)?\
+        \\s*\"?\\s*$"
+    )
+    .unwrap();
+    static ref FUNC_RE: Regex = Regex::new(
+        "^\\s*\"?\\s*function\\s*(!?[A-z0-9]+)\
+        (?:,\\s*subfunction\\s+(\\d+))?\\s*\"?\\s*$"
+    )
+    .unwrap();
+    static ref UNIT_RE: Regex = Regex::new(
+        "^\\s*\"?\\s*unit\\s+([<>]?\\d+)\
+        (?:<>(\\d+))?(?:,\\s*(.*))?\\s*\"?\\s*$"
+    )
+    .unwrap();
+}
+
+#[derive(Debug, PartialEq)]
+pub struct DetectModbusRust {
+    category: Option<Flags<CodeCategory>>,
+    function: Option<FunctionCode>,
+    subfunction: Option<u16>,
+    access_type: Option<Flags<AccessType>>,
+    unit_id: Option<Range<u16>>,
+    address: Option<Range<u16>>,
+    value: Option<Range<u16>>,
+}
+
+/// TODO: remove these after regression testing commit
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_category(modbus: *const DetectModbusRust) -> u8 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.category.map(|val| val.bits()).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_function(modbus: *const DetectModbusRust) -> u8 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.function.map(|val| val as u8).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_subfunction(modbus: *const DetectModbusRust) -> u16 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.subfunction.unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_has_subfunction(modbus: *const DetectModbusRust) -> bool {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.subfunction.is_some()
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_access_type(modbus: *const DetectModbusRust) -> u8 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.access_type.map(|val| val.bits()).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_unit_id_min(modbus: *const DetectModbusRust) -> u16 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.unit_id.as_ref().map(|val| val.start).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_unit_id_max(modbus: *const DetectModbusRust) -> u16 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.unit_id.as_ref().map(|val| val.end).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_address_min(modbus: *const DetectModbusRust) -> u16 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.address.as_ref().map(|val| val.start).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_address_max(modbus: *const DetectModbusRust) -> u16 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.address.as_ref().map(|val| val.end).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_data_min(modbus: *const DetectModbusRust) -> u16 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.value.as_ref().map(|val| val.start).unwrap_or(0)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_get_data_max(modbus: *const DetectModbusRust) -> u16 {
+    let modbus = unsafe { modbus.as_ref() }.unwrap();
+    modbus.value.as_ref().map(|val| val.end).unwrap_or(0)
+}
+
+impl Default for DetectModbusRust {
+    fn default() -> Self {
+        DetectModbusRust {
+            category: None,
+            function: None,
+            subfunction: None,
+            access_type: None,
+            unit_id: None,
+            address: None,
+            value: None,
+        }
+    }
+}
+
+/// Compares a range from the alert signature to the transaction's unit_id/address/value
+/// range. If the signature's range intersects with the transaction, it is a match and true is
+/// returned.
+fn check_match_range(sig_range: &Range<u16>, trans_range: RangeInclusive<u16>) -> bool {
+    if sig_range.start == sig_range.end {
+        sig_range.start >= *trans_range.start() && sig_range.start <= *trans_range.end()
+    } else if sig_range.start == std::u16::MIN {
+        sig_range.end > *trans_range.start()
+    } else if sig_range.end == std::u16::MAX {
+        sig_range.start < *trans_range.end()
+    } else {
+        sig_range.start < *trans_range.end() && *trans_range.start() < sig_range.end
+    }
+}
+
+/// Compares a range from the alert signature to the transaction's unit_id/address/value.
+/// If the signature's range intersects with the transaction, it is a match and true is
+/// returned.
+fn check_match(sig_range: &Range<u16>, value: u16) -> bool {
+    if sig_range.start == sig_range.end {
+        sig_range.start == value
+    } else if sig_range.start == std::u16::MIN {
+        sig_range.end > value
+    } else if sig_range.end == std::u16::MAX {
+        sig_range.start < value
+    } else {
+        sig_range.start < value && value < sig_range.end
+    }
+}
+
+/// Gets the min/max range of an alert signature from the respective capture groups.
+/// In the case where the max is not given, it is set based on the first char of the min str
+/// which indicates what range we are looking for:
+///     '<' = std::u16::MIN..min
+///     '>' = min..std::u16::MAX
+///     _ = min..min
+/// If the max is given, the range returned is min..max
+fn parse_range(min_str: &str, max_str: &str) -> Result<Range<u16>, ()> {
+    if max_str.is_empty() {
+        if let Some(sign) = min_str.chars().next() {
+            match min_str[!sign.is_ascii_digit() as usize..].parse::<u16>() {
+                Ok(num) => match sign {
+                    '>' => Ok(num..std::u16::MAX),
+                    '<' => Ok(std::u16::MIN..num),
+                    _ => Ok(num..num),
+                },
+                Err(_) => {
+                    SCLogError!("Invalid min number: {}", min_str);
+                    Err(())
+                }
+            }
+        } else {
+            Err(())
+        }
+    } else {
+        let min = match min_str.parse::<u16>() {
+            Ok(num) => num,
+            Err(_) => {
+                SCLogError!("Invalid min number: {}", min_str);
+                return Err(());
+            }
+        };
+
+        let max = match max_str.parse::<u16>() {
+            Ok(num) => num,
+            Err(_) => {
+                SCLogError!("Invalid max number: {}", max_str);
+                return Err(());
+            }
+        };
+
+        Ok(min..max)
+    }
+}
+
+/// Intermediary function between the C code and the parsing functions.
+#[no_mangle]
+pub unsafe extern "C" fn rs_modbus_parse(c_arg: *const c_char) -> *mut c_void {
+    if c_arg.is_null() {
+        return std::ptr::null_mut();
+    }
+    if let Ok(arg) = CStr::from_ptr(c_arg).to_str() {
+        match parse_unit_id(&arg)
+            .or_else(|_| parse_function(&arg))
+            .or_else(|_| parse_access(&arg))
+        {
+            Ok(detect) => return Box::into_raw(Box::new(detect)) as *mut c_void,
+            Err(()) => return std::ptr::null_mut(),
+        }
+    }
+    std::ptr::null_mut()
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_modbus_free(ptr: *mut c_void) {
+    if !ptr.is_null() {
+        let _ = Box::from_raw(ptr as *mut DetectModbusRust);
+    }
+}
+
+/// Compares a transaction to a signature to determine whether the transaction
+/// matches the signature. If it does, 1 is returned; otherwise 0 is returned.
+#[no_mangle]
+pub extern "C" fn rs_modbus_inspect(tx: &ModbusTransaction, modbus: &DetectModbusRust) -> u8 {
+    // All necessary information can be found in the request (value inspection currently
+    // only supports write functions, which hold the value in the request).
+    // Only inspect the response in the case where there is no request.
+    let msg = match &tx.request {
+        Some(r) => r,
+        None => match &tx.response {
+            Some(r) => r,
+            None => return 0,
+        },
+    };
+
+    if let Some(unit_id) = &modbus.unit_id {
+        if !check_match(unit_id, msg.unit_id.into()) {
+            return 0;
+        }
+    }
+
+    if let Some(access_type) = &modbus.access_type {
+        let rd_wr_access = *access_type & (AccessType::READ | AccessType::WRITE);
+        let access_func = *access_type & AccessType::FUNC_MASK;
+
+        if rd_wr_access.is_empty()
+            || !msg.access_type.intersects(rd_wr_access)
+            || (!access_func.is_empty() && !msg.access_type.intersects(access_func))
+        {
+            return 0;
+        }
+
+        return inspect_data(msg, modbus) as u8;
+    }
+
+    if let Some(category) = modbus.category {
+        return u8::from(msg.category.intersects(category));
+    }
+
+    match &modbus.function {
+        Some(func) if func == &msg.function.code => match modbus.subfunction {
+            Some(subfunc) => {
+                if let Data::Diagnostic { func, data: _ } = &msg.data {
+                    u8::from(subfunc == func.raw)
+                } else {
+                    0
+                }
+            }
+            None => 1,
+        },
+        None => 1,
+        _ => 0,
+    }
+}
+
+/// Compares the transaction's data with the signature to determine whether or
+/// not it is a match
+fn inspect_data(msg: &Message, modbus: &DetectModbusRust) -> bool {
+    let sig_address = if let Some(sig_addr) = &modbus.address {
+        // Compare the transaction's address with the signature to determine whether or
+        // not it is a match
+        if let Some(req_addr) = msg.get_address_range() {
+            if !check_match_range(sig_addr, req_addr) {
+                return false;
+            }
+        } else {
+            return false;
+        }
+
+        sig_addr.start
+    } else {
+        return true;
+    };
+
+    let sig_value = if let Some(value) = &modbus.value {
+        value
+    } else {
+        return true;
+    };
+
+    if let Some(value) = msg.get_write_value_at_address(&sig_address) {
+        check_match(sig_value, value)
+    } else {
+        false
+    }
+}
+
+/// Parses the access type for the signature
+fn parse_access(access_str: &str) -> Result<DetectModbusRust, ()> {
+    let re = if let Some(re) = ACCESS_RE.captures(access_str) {
+        re
+    } else {
+        return Err(());
+    };
+
+    // 1: Read | Write
+    let mut access_type: Flags<AccessType> = match re.get(1) {
+        Some(access) => match AccessType::from_str(access.as_str()) {
+            Ok(access_type) => access_type.into(),
+            Err(_) => {
+                SCLogError!("Unknown access keyword {}", access.as_str());
+                return Err(());
+            }
+        },
+        None => {
+            SCLogError!("No access keyword found");
+            return Err(());
+        }
+    };
+
+    // 2: Discretes | Coils | Input | Holding
+    access_type = match re.get(2) {
+        Some(x) if x.as_str() == "coils" => access_type | AccessType::COILS,
+        Some(x) if x.as_str() == "holding" => access_type | AccessType::HOLDING,
+        Some(x) if x.as_str() == "discretes" => {
+            if access_type == AccessType::WRITE {
+                SCLogError!("Discrete access is only read access");
+                return Err(());
+            }
+            access_type | AccessType::DISCRETES
+        }
+        Some(x) if x.as_str() == "input" => {
+            if access_type == AccessType::WRITE {
+                SCLogError!("Input access is only read access");
+                return Err(());
+            }
+            access_type | AccessType::INPUT
+        }
+        Some(unknown) => {
+            SCLogError!("Unknown access keyword {}", unknown.as_str());
+            return Err(());
+        }
+        None => access_type,
+    };
+
+    // 3: Address min
+    let address = if let Some(min) = re.get(3) {
+        // 4: Address max
+        let max_str = if let Some(max) = re.get(4) {
+            max.as_str()
+        } else {
+            ""
+        };
+        parse_range(min.as_str(), max_str)?
+    } else {
+        return Ok(DetectModbusRust {
+            access_type: Some(access_type),
+            ..Default::default()
+        });
+    };
+
+    // 5: Value min
+    let value = if let Some(min) = re.get(5) {
+        if address.start != address.end {
+            SCLogError!("rule contains conflicting keywords (address range and value).");
+            return Err(());
+        }
+
+        if access_type == AccessType::READ {
+            SCLogError!("Value keyword only works in write access");
+            return Err(());
+        }
+
+        // 6: Value max
+        let max_str = if let Some(max) = re.get(6) {
+            max.as_str()
+        } else {
+            ""
+        };
+
+        parse_range(min.as_str(), max_str)?
+    } else {
+        return Ok(DetectModbusRust {
+            access_type: Some(access_type),
+            address: Some(address),
+            ..Default::default()
+        });
+    };
+
+    Ok(DetectModbusRust {
+        access_type: Some(access_type),
+        address: Some(address),
+        value: Some(value),
+        ..Default::default()
+    })
+}
+
+fn parse_function(func_str: &str) -> Result<DetectModbusRust, ()> {
+    let re = if let Some(re) = FUNC_RE.captures(func_str) {
+        re
+    } else {
+        return Err(());
+    };
+
+    let mut modbus: DetectModbusRust = Default::default();
+
+    // 1: Function
+    if let Some(x) = re.get(1) {
+        let word = x.as_str();
+
+        // Digit
+        if let Ok(num) = word.parse::<u8>() {
+            if num == 0 {
+                SCLogError!("Invalid modbus function value");
+                return Err(());
+            }
+
+            modbus.function = Some(FunctionCode::from_raw(num));
+
+            // 2: Subfunction (optional)
+            match re.get(2) {
+                Some(x) => {
+                    let subfunc = x.as_str();
+                    match subfunc.parse::<u16>() {
+                        Ok(num) => {
+                            modbus.subfunction = Some(num);
+                        }
+                        Err(_) => {
+                            SCLogError!("Invalid subfunction value: {}", subfunc);
+                            return Err(());
+                        }
+                    }
+                }
+                None => return Ok(modbus),
+            }
+        }
+        // Non-digit
+        else {
+            let neg = word.starts_with('!');
+
+            let category = match &word[neg as usize..] {
+                "assigned" => CodeCategory::PUBLIC_ASSIGNED.into(),
+                "unassigned" => CodeCategory::PUBLIC_UNASSIGNED.into(),
+                "public" => CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED,
+                "user" => CodeCategory::USER_DEFINED.into(),
+                "reserved" => CodeCategory::RESERVED.into(),
+                "all" => {
+                    CodeCategory::PUBLIC_ASSIGNED
+                        | CodeCategory::PUBLIC_UNASSIGNED
+                        | CodeCategory::USER_DEFINED
+                        | CodeCategory::RESERVED
+                }
+                _ => {
+                    SCLogError!("Keyword unknown: {}", word);
+                    return Err(());
+                }
+            };
+
+            if neg {
+                modbus.category = Some(!category);
+            } else {
+                modbus.category = Some(category);
+            }
+        }
+    } else {
+        return Err(());
+    }
+
+    Ok(modbus)
+}
+
+fn parse_unit_id(unit_str: &str) -> Result<DetectModbusRust, ()> {
+    let re = if let Some(re) = UNIT_RE.captures(unit_str) {
+        re
+    } else {
+        return Err(());
+    };
+
+    // 3: Either function or access string
+    let mut modbus = if let Some(x) = re.get(3) {
+        let extra = x.as_str();
+        if let Ok(mbus) = parse_function(extra) {
+            mbus
+        } else if let Ok(mbus) = parse_access(extra) {
+            mbus
+        } else {
+            SCLogError!("Invalid modbus option: {}", extra);
+            return Err(());
+        }
+    } else {
+        Default::default()
+    };
+
+    // 1: Unit ID min
+    if let Some(min) = re.get(1) {
+        // 2: Unit ID max
+        let max_str = if let Some(max) = re.get(2) {
+            max.as_str()
+        } else {
+            ""
+        };
+
+        modbus.unit_id = Some(parse_range(min.as_str(), max_str)?);
+    } else {
+        SCLogError!("Min modbus unit ID not found");
+        return Err(());
+    }
+
+    Ok(modbus)
+}
diff --git a/rust/src/modbus/mod.rs b/rust/src/modbus/mod.rs
new file mode 100644 (file)
index 0000000..58b9951
--- /dev/null
@@ -0,0 +1,19 @@
+/* Copyright (C) 2021 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
+ * Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * version 2 along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+ * 02110-1301, USA.
+ */
+
+pub mod detect;
+pub mod modbus;
diff --git a/rust/src/modbus/modbus.rs b/rust/src/modbus/modbus.rs
new file mode 100644 (file)
index 0000000..f88f5cc
--- /dev/null
@@ -0,0 +1,1587 @@
+/* Copyright (C) 2021 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
+* Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* version 2 along with this program; if not, write to the Free Software
+* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+* 02110-1301, USA.
+*/
+use crate::applayer::*;
+use crate::core::{self, AppProto, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP};
+
+use std::ffi::CString;
+use std::str::FromStr;
+
+use sawp::error::Error as SawpError;
+use sawp::error::ErrorKind as SawpErrorKind;
+use sawp::parser::{Direction, Parse};
+use sawp::probe::{Probe, Status};
+use sawp_modbus::{self, AccessType, ErrorFlags, Flags, Message};
+
+pub const REQUEST_FLOOD: usize = 500; // Default unreplied Modbus requests are considered a flood
+pub const MODBUS_PARSER: sawp_modbus::Modbus = sawp_modbus::Modbus {};
+
+static mut ALPROTO_MODBUS: AppProto = ALPROTO_UNKNOWN;
+
+enum ModbusEvent {
+    UnsolicitedResponse = 0,
+    InvalidFunctionCode,
+    InvalidLength,
+    InvalidValue,
+    InvalidExceptionCode,
+    ValueMismatch,
+    Flooded,
+    InvalidProtocolId,
+}
+
+impl FromStr for ModbusEvent {
+    type Err = ();
+    fn from_str(name: &str) -> Result<ModbusEvent, Self::Err> {
+        match name {
+            "unsolicited_response" => Ok(ModbusEvent::UnsolicitedResponse),
+            "invalid_function_code" => Ok(ModbusEvent::InvalidFunctionCode),
+            "invalid_length" => Ok(ModbusEvent::InvalidLength),
+            "invalid_value" => Ok(ModbusEvent::InvalidValue),
+            "invalid_exception_code" => Ok(ModbusEvent::InvalidExceptionCode),
+            "value_mismatch" => Ok(ModbusEvent::ValueMismatch),
+            "flooded" => Ok(ModbusEvent::Flooded),
+            "invalid_protocol_id" => Ok(ModbusEvent::InvalidProtocolId),
+            _ => Err(()),
+        }
+    }
+}
+
+impl ModbusEvent {
+    pub fn to_str(&self) -> &str {
+        match *self {
+            ModbusEvent::UnsolicitedResponse => "unsolicited_response",
+            ModbusEvent::InvalidFunctionCode => "invalid_function_code",
+            ModbusEvent::InvalidLength => "invalid_length",
+            ModbusEvent::InvalidValue => "invalid_value",
+            ModbusEvent::InvalidExceptionCode => "invalid_exception_code",
+            ModbusEvent::ValueMismatch => "value_mismatch",
+            ModbusEvent::Flooded => "flooded",
+            ModbusEvent::InvalidProtocolId => "invalid_protocol_id",
+        }
+    }
+
+    pub fn from_id(id: u32) -> Option<Self> {
+        match id {
+            0 => Some(ModbusEvent::UnsolicitedResponse),
+            1 => Some(ModbusEvent::InvalidFunctionCode),
+            2 => Some(ModbusEvent::InvalidLength),
+            3 => Some(ModbusEvent::InvalidValue),
+            4 => Some(ModbusEvent::InvalidExceptionCode),
+            5 => Some(ModbusEvent::ValueMismatch),
+            6 => Some(ModbusEvent::Flooded),
+            7 => Some(ModbusEvent::InvalidProtocolId),
+            _ => None,
+        }
+    }
+}
+
+pub struct ModbusTransaction {
+    pub id: u64,
+
+    pub request: Option<Message>,
+    pub response: Option<Message>,
+
+    pub events: *mut core::AppLayerDecoderEvents,
+    pub de_state: Option<*mut core::DetectEngineState>,
+    pub tx_data: AppLayerTxData,
+}
+
+impl ModbusTransaction {
+    pub fn new(id: u64) -> Self {
+        Self {
+            id,
+            request: None,
+            response: None,
+            events: std::ptr::null_mut(),
+            de_state: None,
+            tx_data: AppLayerTxData::new(),
+        }
+    }
+
+    fn set_event(&mut self, event: ModbusEvent) {
+        core::sc_app_layer_decoder_events_set_event_raw(&mut self.events, event as u8);
+    }
+
+    fn set_events_from_flags(&mut self, flags: &Flags<ErrorFlags>) {
+        if flags.intersects(ErrorFlags::FUNC_CODE) {
+            self.set_event(ModbusEvent::InvalidFunctionCode);
+        }
+        if flags.intersects(ErrorFlags::DATA_VALUE) {
+            self.set_event(ModbusEvent::InvalidValue);
+        }
+        if flags.intersects(ErrorFlags::DATA_LENGTH) {
+            self.set_event(ModbusEvent::InvalidLength);
+        }
+        if flags.intersects(ErrorFlags::EXC_CODE) {
+            self.set_event(ModbusEvent::InvalidExceptionCode);
+        }
+        if flags.intersects(ErrorFlags::PROTO_ID) {
+            self.set_event(ModbusEvent::InvalidProtocolId);
+        }
+    }
+}
+
+impl Drop for ModbusTransaction {
+    fn drop(&mut self) {
+        if !self.events.is_null() {
+            core::sc_app_layer_decoder_events_free_events(&mut self.events);
+        }
+
+        if let Some(state) = self.de_state {
+            core::sc_detect_engine_state_free(state);
+        }
+    }
+}
+
+pub struct ModbusState {
+    pub transactions: Vec<ModbusTransaction>,
+    tx_id: u64,
+    givenup: bool, // Indicates flood
+}
+
+impl ModbusState {
+    pub fn new() -> Self {
+        Self {
+            transactions: Vec::new(),
+            tx_id: 0,
+            givenup: false,
+        }
+    }
+
+    pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut ModbusTransaction> {
+        for tx in &mut self.transactions {
+            if tx.id == tx_id + 1 {
+                return Some(tx);
+            }
+        }
+        None
+    }
+
+    /// Searches the requests in order to find one matching the given response. Returns the matching
+    /// transaction, if it exists
+    pub fn find_request_and_validate(
+        &mut self, resp: &mut Message,
+    ) -> Option<&mut ModbusTransaction> {
+        for tx in &mut self.transactions {
+            if let Some(req) = &tx.request {
+                if tx.response.is_none() && resp.matches(req) {
+                    return Some(tx);
+                }
+            }
+        }
+        None
+    }
+
+    /// Searches the responses in order to find one matching the given request. Returns the matching
+    /// transaction, if it exists
+    pub fn find_response_and_validate(
+        &mut self, req: &mut Message,
+    ) -> Option<&mut ModbusTransaction> {
+        for tx in &mut self.transactions {
+            if let Some(resp) = &tx.response {
+                if tx.request.is_none() && req.matches(resp) {
+                    return Some(tx);
+                }
+            }
+        }
+        None
+    }
+
+    pub fn new_tx(&mut self) -> Option<ModbusTransaction> {
+        // Check flood limit
+        if self.givenup {
+            return None;
+        }
+
+        self.tx_id += 1;
+        let mut tx = ModbusTransaction::new(self.tx_id);
+
+        if REQUEST_FLOOD != 0 && self.transactions.len() >= REQUEST_FLOOD {
+            tx.set_event(ModbusEvent::Flooded);
+            self.givenup = true;
+        }
+
+        Some(tx)
+    }
+
+    pub fn free_tx(&mut self, tx_id: u64) {
+        if let Some(index) = self.transactions.iter().position(|tx| tx.id == tx_id + 1) {
+            self.transactions.remove(index);
+
+            // Check flood limit
+            if self.givenup && REQUEST_FLOOD != 0 && self.transactions.len() < REQUEST_FLOOD {
+                self.givenup = false;
+            }
+        }
+    }
+
+    pub fn parse(&mut self, input: &[u8], direction: Direction) -> AppLayerResult {
+        let mut rest = input;
+        while rest.len() > 0 {
+            match MODBUS_PARSER.parse(rest, direction.clone()) {
+                Ok((inner_rest, Some(mut msg))) => {
+                    match direction {
+                        Direction::ToServer | Direction::Unknown => {
+                            match self.find_response_and_validate(&mut msg) {
+                                Some(tx) => {
+                                    tx.set_events_from_flags(&msg.error_flags);
+                                    tx.request = Some(msg);
+                                }
+                                None => {
+                                    let mut tx = match self.new_tx() {
+                                        Some(tx) => tx,
+                                        None => return AppLayerResult::ok(),
+                                    };
+                                    tx.set_events_from_flags(&msg.error_flags);
+                                    tx.request = Some(msg);
+                                    self.transactions.push(tx);
+                                }
+                            }
+                        }
+                        Direction::ToClient => match self.find_request_and_validate(&mut msg) {
+                            Some(tx) => {
+                                if msg
+                                    .access_type
+                                    .intersects(AccessType::READ | AccessType::WRITE)
+                                    && msg.error_flags.intersects(
+                                        ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE,
+                                    )
+                                {
+                                    tx.set_event(ModbusEvent::ValueMismatch);
+                                } else {
+                                    tx.set_events_from_flags(&msg.error_flags);
+                                }
+                                tx.response = Some(msg);
+                            }
+                            None => {
+                                let mut tx = match self.new_tx() {
+                                    Some(tx) => tx,
+                                    None => return AppLayerResult::ok(),
+                                };
+                                if msg
+                                    .access_type
+                                    .intersects(AccessType::READ | AccessType::WRITE)
+                                    && msg.error_flags.intersects(
+                                        ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE,
+                                    )
+                                {
+                                    tx.set_event(ModbusEvent::ValueMismatch);
+                                } else {
+                                    tx.set_events_from_flags(&msg.error_flags);
+                                }
+                                tx.response = Some(msg);
+                                tx.set_event(ModbusEvent::UnsolicitedResponse);
+                                self.transactions.push(tx);
+                            }
+                        },
+                    }
+
+                    if inner_rest.len() >= rest.len() {
+                        return AppLayerResult::err();
+                    }
+                    rest = inner_rest;
+                }
+                Ok((inner_rest, None)) => {
+                    return AppLayerResult::incomplete(
+                        (input.len() - inner_rest.len()) as u32,
+                        inner_rest.len() as u32 + 1,
+                    );
+                }
+                Err(SawpError {
+                    kind: SawpErrorKind::Incomplete(sawp::error::Needed::Size(needed)),
+                }) => {
+                    return AppLayerResult::incomplete(
+                        (input.len() - rest.len()) as u32,
+                        (rest.len() + needed.get()) as u32,
+                    );
+                }
+                Err(SawpError {
+                    kind: SawpErrorKind::Incomplete(sawp::error::Needed::Unknown),
+                }) => {
+                    return AppLayerResult::incomplete(
+                        (input.len() - rest.len()) as u32,
+                        rest.len() as u32 + 1,
+                    );
+                }
+                Err(_) => return AppLayerResult::err(),
+            }
+        }
+        AppLayerResult::ok()
+    }
+}
+
+/// Probe input to see if it looks like Modbus.
+#[no_mangle]
+pub extern "C" fn rs_modbus_probe(
+    _flow: *const core::Flow, _direction: u8, input: *const u8, len: u32, _rdir: *mut u8,
+) -> AppProto {
+    let slice: &[u8] = unsafe { std::slice::from_raw_parts(input as *mut u8, len as usize) };
+    match MODBUS_PARSER.probe(slice, Direction::Unknown) {
+        Status::Recognized => unsafe { ALPROTO_MODBUS },
+        Status::Incomplete => ALPROTO_UNKNOWN,
+        Status::Unrecognized => unsafe { ALPROTO_FAILED },
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_new(
+    _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto,
+) -> *mut std::os::raw::c_void {
+    Box::into_raw(Box::new(ModbusState::new())) as *mut std::os::raw::c_void
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_free(state: *mut std::os::raw::c_void) {
+    let _state: Box<ModbusState> = unsafe { Box::from_raw(state as *mut ModbusState) };
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) {
+    let state = cast_pointer!(state, ModbusState);
+    state.free_tx(tx_id);
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_parse_request(
+    _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
+    input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
+) -> AppLayerResult {
+    if input_len == 0 {
+        if unsafe { AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) } > 0 {
+            return AppLayerResult::ok();
+        } else {
+            return AppLayerResult::err();
+        }
+    }
+
+    let state = cast_pointer!(state, ModbusState);
+    let buf = unsafe { std::slice::from_raw_parts(input, input_len as usize) };
+
+    state.parse(buf, Direction::ToServer)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_parse_response(
+    _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void,
+    input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
+) -> AppLayerResult {
+    if input_len == 0 {
+        unsafe {
+            if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 {
+                return AppLayerResult::ok();
+            } else {
+                return AppLayerResult::err();
+            }
+        }
+    }
+
+    let state = cast_pointer!(state, ModbusState);
+    let buf = unsafe { std::slice::from_raw_parts(input, input_len as usize) };
+
+    state.parse(buf, Direction::ToClient)
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 {
+    let state = cast_pointer!(state, ModbusState);
+    state.tx_id
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_tx(
+    state: *mut std::os::raw::c_void, tx_id: u64,
+) -> *mut std::os::raw::c_void {
+    let state = cast_pointer!(state, ModbusState);
+    match state.get_tx(tx_id) {
+        Some(tx) => (tx as *mut ModbusTransaction) as *mut std::os::raw::c_void,
+        None => std::ptr::null_mut(),
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_tx_get_alstate_progress(
+    tx: *mut std::os::raw::c_void, _direction: u8,
+) -> std::os::raw::c_int {
+    let tx = cast_pointer!(tx, ModbusTransaction);
+    tx.response.is_some() as std::os::raw::c_int
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_events(
+    tx: *mut std::os::raw::c_void,
+) -> *mut core::AppLayerDecoderEvents {
+    let tx = cast_pointer!(tx, ModbusTransaction);
+    tx.events
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_event_info(
+    event_name: *const std::os::raw::c_char, event_id: *mut std::os::raw::c_int,
+    event_type: *mut core::AppLayerEventType,
+) -> std::os::raw::c_int {
+    if event_name.is_null() {
+        return -1;
+    }
+
+    let event_name = unsafe { std::ffi::CStr::from_ptr(event_name) };
+    if let Ok(event_name) = event_name.to_str() {
+        match ModbusEvent::from_str(event_name) {
+            Ok(event) => unsafe {
+                *event_id = event as std::os::raw::c_int;
+                *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
+                0
+            },
+            Err(_) => {
+                SCLogError!(
+                    "event {} not present in modbus's enum map table.",
+                    event_name
+                );
+                -1
+            }
+        }
+    } else {
+        -1
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_event_info_by_id(
+    event_id: std::os::raw::c_int, event_name: *mut *const std::os::raw::c_char,
+    event_type: *mut core::AppLayerEventType,
+) -> i8 {
+    if let Some(e) = ModbusEvent::from_id(event_id as u32) {
+        unsafe {
+            *event_name = e.to_str().as_ptr() as *const std::os::raw::c_char;
+            *event_type = core::APP_LAYER_EVENT_TYPE_TRANSACTION;
+        }
+        0
+    } else {
+        SCLogError!("event {} not present in modbus's enum map table.", event_id);
+        -1
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_tx_detect_state(
+    tx: *mut std::os::raw::c_void,
+) -> *mut core::DetectEngineState {
+    let tx = cast_pointer!(tx, ModbusTransaction);
+    match tx.de_state {
+        Some(ds) => ds,
+        None => std::ptr::null_mut(),
+    }
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_set_tx_detect_state(
+    tx: *mut std::os::raw::c_void, de_state: &mut core::DetectEngineState,
+) -> std::os::raw::c_int {
+    let tx = cast_pointer!(tx, ModbusTransaction);
+    tx.de_state = Some(de_state);
+    0
+}
+
+#[no_mangle]
+pub extern "C" fn rs_modbus_state_get_tx_data(
+    tx: *mut std::os::raw::c_void,
+) -> *mut AppLayerTxData {
+    let tx = cast_pointer!(tx, ModbusTransaction);
+    &mut tx.tx_data
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn rs_modbus_register_parser() {
+    let default_port = std::ffi::CString::new("[502]").unwrap();
+    let parser = RustParser {
+        name: b"modbus\0".as_ptr() as *const std::os::raw::c_char,
+        default_port: default_port.as_ptr(),
+        ipproto: IPPROTO_TCP,
+        probe_ts: Some(rs_modbus_probe),
+        probe_tc: Some(rs_modbus_probe),
+        min_depth: 0,
+        max_depth: 16,
+        state_new: rs_modbus_state_new,
+        state_free: rs_modbus_state_free,
+        tx_free: rs_modbus_state_tx_free,
+        parse_ts: rs_modbus_parse_request,
+        parse_tc: rs_modbus_parse_response,
+        get_tx_count: rs_modbus_state_get_tx_count,
+        get_tx: rs_modbus_state_get_tx,
+        tx_comp_st_ts: 1,
+        tx_comp_st_tc: 1,
+        tx_get_progress: rs_modbus_tx_get_alstate_progress,
+        get_events: Some(rs_modbus_state_get_events),
+        get_eventinfo: Some(rs_modbus_state_get_event_info),
+        get_eventinfo_byid: Some(rs_modbus_state_get_event_info_by_id),
+        localstorage_new: None,
+        localstorage_free: None,
+        get_files: None,
+        get_tx_iterator: None,
+        get_de_state: rs_modbus_state_get_tx_detect_state,
+        set_de_state: rs_modbus_state_set_tx_detect_state,
+        get_tx_data: rs_modbus_state_get_tx_data,
+        apply_tx_config: None,
+        flags: APP_LAYER_PARSER_OPT_ACCEPT_GAPS,
+        truncate: None,
+    };
+
+    let ip_proto_str = CString::new("tcp").unwrap();
+    if AppLayerProtoDetectConfProtoDetectionEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+        let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
+        ALPROTO_MODBUS = alproto;
+        if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
+            let _ = AppLayerRegisterParser(&parser, alproto);
+        }
+    }
+}
+
+// This struct and accessor functions are used for app-layer-modbus.c tests.
+pub mod test {
+    use super::ModbusState;
+    use sawp_modbus::{Data, Message, Read, Write};
+    use std::ffi::c_void;
+    #[repr(C)]
+    pub struct ModbusMessage(*const c_void);
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_function(msg: *const ModbusMessage) -> u8 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        msg.function.raw
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_subfunction(msg: *const ModbusMessage) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Diagnostic { func, data: _ } = &msg.data {
+            func.raw
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_read_request_address(
+        msg: *const ModbusMessage,
+    ) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Read(Read::Request {
+            address,
+            quantity: _,
+        }) = &msg.data
+        {
+            *address
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_read_request_quantity(
+        msg: *const ModbusMessage,
+    ) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Read(Read::Request {
+            address: _,
+            quantity,
+        }) = &msg.data
+        {
+            *quantity
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_address(
+        msg: *const ModbusMessage,
+    ) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::ReadWrite {
+            read:
+                Read::Request {
+                    address,
+                    quantity: _,
+                },
+            write: _,
+        } = &msg.data
+        {
+            *address
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_quantity(
+        msg: *const ModbusMessage,
+    ) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::ReadWrite {
+            read:
+                Read::Request {
+                    address: _,
+                    quantity,
+                },
+            write: _,
+        } = &msg.data
+        {
+            *quantity
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_address(
+        msg: *const ModbusMessage,
+    ) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::ReadWrite {
+            read: _,
+            write:
+                Write::MultReq {
+                    address,
+                    quantity: _,
+                    data: _,
+                },
+        } = &msg.data
+        {
+            *address
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_quantity(
+        msg: *const ModbusMessage,
+    ) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::ReadWrite {
+            read: _,
+            write:
+                Write::MultReq {
+                    address: _,
+                    quantity,
+                    data: _,
+                },
+        } = &msg.data
+        {
+            *quantity
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_data(
+        msg: *const ModbusMessage, data_len: *mut usize,
+    ) -> *const u8 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::ReadWrite {
+            read: _,
+            write:
+                Write::MultReq {
+                    address: _,
+                    quantity: _,
+                    data,
+                },
+        } = &msg.data
+        {
+            *data_len = data.len();
+            data.as_slice().as_ptr()
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_address(
+        msg: *const ModbusMessage,
+    ) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Write(Write::MultReq {
+            address,
+            quantity: _,
+            data: _,
+        }) = &msg.data
+        {
+            *address
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_quantity(
+        msg: *const ModbusMessage,
+    ) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Write(Write::MultReq {
+            address: _,
+            quantity,
+            data: _,
+        }) = &msg.data
+        {
+            *quantity
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_data(
+        msg: *const ModbusMessage, data_len: *mut usize,
+    ) -> *const u8 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Write(Write::MultReq {
+            address: _,
+            quantity: _,
+            data,
+        }) = &msg.data
+        {
+            *data_len = data.len();
+            data.as_slice().as_ptr()
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_and_mask(msg: *const ModbusMessage) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Write(Write::Mask {
+            address: _,
+            and_mask,
+            or_mask: _,
+        }) = &msg.data
+        {
+            *and_mask
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_or_mask(msg: *const ModbusMessage) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Write(Write::Mask {
+            address: _,
+            and_mask: _,
+            or_mask,
+        }) = &msg.data
+        {
+            *or_mask
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_write_address(msg: *const ModbusMessage) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Write(Write::Other { address, data: _ }) = &msg.data {
+            *address
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_write_data(msg: *const ModbusMessage) -> u16 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::Write(Write::Other { address: _, data }) = &msg.data {
+            *data
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn rs_modbus_message_get_bytevec_data(
+        msg: *const ModbusMessage, data_len: *mut usize,
+    ) -> *const u8 {
+        let msg = msg.as_ref().unwrap().0 as *const Message;
+        let msg = msg.as_ref().unwrap();
+        if let Data::ByteVec(data) = &msg.data {
+            *data_len = data.len();
+            data.as_slice().as_ptr()
+        } else {
+            panic!("wrong modbus message data type");
+        }
+    }
+
+    #[no_mangle]
+    pub extern "C" fn rs_modbus_state_get_tx_request(
+        state: *mut std::os::raw::c_void, tx_id: u64,
+    ) -> ModbusMessage {
+        let state = cast_pointer!(state, ModbusState);
+        if let Some(tx) = state.get_tx(tx_id) {
+            if let Some(request) = &tx.request {
+                ModbusMessage((request as *const Message) as *const c_void)
+            } else {
+                ModbusMessage(std::ptr::null())
+            }
+        } else {
+            ModbusMessage(std::ptr::null())
+        }
+    }
+
+    #[no_mangle]
+    pub extern "C" fn rs_modbus_state_get_tx_response(
+        state: *mut std::os::raw::c_void, tx_id: u64,
+    ) -> ModbusMessage {
+        let state = cast_pointer!(state, ModbusState);
+        if let Some(tx) = state.get_tx(tx_id) {
+            if let Some(response) = &tx.response {
+                ModbusMessage((response as *const Message) as *const c_void)
+            } else {
+                ModbusMessage(std::ptr::null())
+            }
+        } else {
+            ModbusMessage(std::ptr::null())
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use sawp_modbus::{
+        Data, Diagnostic, DiagnosticSubfunction, Exception, ExceptionCode, FunctionCode, Read,
+        Write,
+    };
+
+    const INVALID_FUNC_CODE: &[u8] = &[
+        0x00, 0x00, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x02, // Length
+        0x00, // Unit ID
+        0x00, // Function code
+    ];
+
+    const RD_COILS_REQ: &[u8] = &[
+        0x00, 0x00, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x06, // Length
+        0x00, // Unit ID
+        0x01, // Function code
+        0x78, 0x90, // Starting Address
+        0x00, 0x13, // Quantity of coils
+    ];
+
+    const RD_COILS_RESP: &[u8] = &[
+        0x00, 0x00, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x06, // Length
+        0x00, // Unit ID
+        0x01, // Function code
+        0x03, // Byte count
+        0xCD, 0x6B, 0x05, // Coil Status
+    ];
+
+    const RD_COILS_ERR_RESP: &[u8] = &[
+        0x00, 0x00, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x03, // Length
+        0x00, // Unit ID
+        0x81, // Function code
+        0xFF, // Exception code
+    ];
+
+    const WR_SINGLE_REG_REQ: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x06, // Length
+        0x00, // Unit ID
+        0x06, // Function code
+        0x00, 0x01, // Register Address
+        0x00, 0x03, // Register Value
+    ];
+
+    const INVALID_WR_SINGLE_REG_REQ: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x04, // Length
+        0x00, // Unit ID
+        0x06, // Function code
+        0x00, 0x01, // Register Address
+    ];
+
+    const WR_SINGLE_REG_RESP: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x06, // Length
+        0x00, // Unit ID
+        0x06, // Function code
+        0x00, 0x01, // Register Address
+        0x00, 0x03, // Register Value
+    ];
+
+    const WR_MULT_REG_REQ: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x0B, // Length
+        0x00, // Unit ID
+        0x10, // Function code
+        0x00, 0x01, // Starting Address
+        0x00, 0x02, // Quantity of Registers
+        0x04, // Byte count
+        0x00, 0x0A, // Registers Value
+        0x01, 0x02,
+    ];
+
+    const INVALID_PDU_WR_MULT_REG_REQ: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x02, // Length
+        0x00, // Unit ID
+        0x10, // Function code
+    ];
+
+    const WR_MULT_REG_RESP: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x06, // Length
+        0x00, // Unit ID
+        0x10, // Function code
+        0x00, 0x01, // Starting Address
+        0x00, 0x02, // Quantity of Registers
+    ];
+
+    const MASK_WR_REG_REQ: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x08, // Length
+        0x00, // Unit ID
+        0x16, // Function code
+        0x00, 0x04, // Reference Address
+        0x00, 0xF2, // And_Mask
+        0x00, 0x25, // Or_Mask
+    ];
+
+    const INVALID_MASK_WR_REG_REQ: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x06, // Length
+        0x00, // Unit ID
+        0x16, // Function code
+        0x00, 0x04, // Reference Address
+        0x00, 0xF2, // And_Mask
+    ];
+
+    const MASK_WR_REG_RESP: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x08, // Length
+        0x00, // Unit ID
+        0x16, // Function code
+        0x00, 0x04, // Reference Address
+        0x00, 0xF2, // And_Mask
+        0x00, 0x25, // Or_Mask
+    ];
+
+    const RD_WR_MULT_REG_REQ: &[u8] = &[
+        0x12, 0x34, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x11, // Length
+        0x00, // Unit ID
+        0x17, // Function code
+        0x00, 0x03, // Read Starting Address
+        0x00, 0x06, // Quantity to Read
+        0x00, 0x0E, // Write Starting Address
+        0x00, 0x03, // Quantity to Write
+        0x06, // Write Byte count
+        0x12, 0x34, // Write Registers Value
+        0x56, 0x78, 0x9A, 0xBC,
+    ];
+
+    // Mismatch value in Byte count 0x0B instead of 0x0C
+    const RD_WR_MULT_REG_RESP: &[u8] = &[
+        0x12, 0x34, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x0E, // Length
+        0x00, // Unit ID
+        0x17, // Function code
+        0x0B, // Byte count
+        0x00, 0xFE, // Read Registers Value
+        0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00,
+    ];
+
+    const FORCE_LISTEN_ONLY_MODE: &[u8] = &[
+        0x0A, 0x00, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x06, // Length
+        0x00, // Unit ID
+        0x08, // Function code
+        0x00, 0x04, // Sub-function code
+        0x00, 0x00, // Data
+    ];
+
+    const INVALID_PROTO_REQ: &[u8] = &[
+        0x00, 0x00, // Transaction ID
+        0x00, 0x01, // Protocol ID
+        0x00, 0x06, // Length
+        0x00, // Unit ID
+        0x01, // Function code
+        0x78, 0x90, // Starting Address
+        0x00, 0x13, // Quantity of coils
+    ];
+
+    const INVALID_LEN_WR_MULT_REG_REQ: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0x00, 0x09, // Length
+        0x00, // Unit ID
+        0x10, // Function code
+        0x00, 0x01, // Starting Address
+        0x00, 0x02, // Quantity of Registers
+        0x04, // Byte count
+        0x00, 0x0A, // Registers Value
+        0x01, 0x02,
+    ];
+
+    const EXCEEDED_LEN_WR_MULT_REG_REQ: &[u8] = &[
+        0x00, 0x0A, // Transaction ID
+        0x00, 0x00, // Protocol ID
+        0xff, 0xfa, // Length
+        0x00, // Unit ID
+        0x10, // Function code
+        0x00, 0x01, // Starting Address
+        0x7f, 0xf9, // Quantity of Registers
+        0xff, // Byte count
+    ];
+
+    #[test]
+    fn read_coils() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&RD_COILS_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::RdCoils);
+        assert_eq!(
+            msg.data,
+            Data::Read(Read::Request {
+                address: 0x7890,
+                quantity: 0x0013
+            })
+        );
+
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&RD_COILS_RESP, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::RdCoils);
+        assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05])));
+    }
+
+    #[test]
+    fn write_multiple_registers() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&WR_MULT_REG_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::MultReq {
+                address: 0x0001,
+                quantity: 0x0002,
+                data: vec![0x00, 0x0a, 0x01, 0x02],
+            })
+        );
+
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&WR_MULT_REG_RESP, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::Other {
+                address: 0x0001,
+                data: 0x0002
+            })
+        );
+    }
+
+    #[test]
+    fn read_write_multiple_registers() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&RD_WR_MULT_REG_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs);
+        assert_eq!(
+            msg.data,
+            Data::ReadWrite {
+                read: Read::Request {
+                    address: 0x0003,
+                    quantity: 0x0006,
+                },
+                write: Write::MultReq {
+                    address: 0x000e,
+                    quantity: 0x0003,
+                    data: vec![0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc]
+                }
+            }
+        );
+
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&RD_WR_MULT_REG_RESP, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs);
+        assert_eq!(
+            msg.data,
+            Data::Read(Read::Response(vec![
+                0x00, 0xFE, 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00,
+            ]))
+        );
+    }
+
+    #[test]
+    fn force_listen_only_mode() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&FORCE_LISTEN_ONLY_MODE, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::Diagnostic);
+        assert_eq!(
+            msg.data,
+            Data::Diagnostic {
+                func: Diagnostic {
+                    raw: 4,
+                    code: DiagnosticSubfunction::ForceListenOnlyMode
+                },
+                data: vec![0x00, 0x00]
+            }
+        );
+    }
+
+    #[test]
+    fn invalid_protocol_version() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&INVALID_PROTO_REQ, Direction::ToServer)
+        );
+
+        assert_eq!(state.transactions.len(), 1);
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.error_flags, ErrorFlags::PROTO_ID);
+    }
+
+    #[test]
+    fn unsolicited_response() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&RD_COILS_RESP, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::RdCoils);
+        assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05])));
+    }
+
+    #[test]
+    fn invalid_length_request() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::incomplete(15, 4),
+            state.parse(&INVALID_LEN_WR_MULT_REG_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::MultReq {
+                address: 0x0001,
+                quantity: 0x0002,
+                data: vec![0x00, 0x0a]
+            })
+        );
+        assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH);
+    }
+
+    #[test]
+    fn exception_code_invalid() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&RD_COILS_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::RdCoils);
+        assert_eq!(
+            msg.data,
+            Data::Read(Read::Request {
+                address: 0x7890,
+                quantity: 0x0013
+            })
+        );
+
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&RD_COILS_ERR_RESP, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(
+            msg.data,
+            Data::Exception(Exception {
+                raw: 255,
+                code: ExceptionCode::Unknown
+            })
+        );
+        assert_eq!(msg.error_flags, ErrorFlags::EXC_CODE);
+    }
+
+    #[test]
+    fn fragmentation_1_adu_in_2_tcp_packets() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::incomplete(0, 15),
+            state.parse(
+                &RD_COILS_REQ[0..(RD_COILS_REQ.len() - 3)],
+                Direction::ToServer
+            )
+        );
+        assert_eq!(state.transactions.len(), 0);
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&RD_COILS_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        assert!(&tx.request.is_some());
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::RdCoils);
+        assert_eq!(
+            msg.data,
+            Data::Read(Read::Request {
+                address: 0x7890,
+                quantity: 0x0013
+            })
+        );
+    }
+
+    #[test]
+    fn fragmentation_2_adu_in_1_tcp_packet() {
+        let req = [RD_COILS_REQ, WR_MULT_REG_REQ].concat();
+        let resp = [RD_COILS_RESP, WR_MULT_REG_RESP].concat();
+
+        let mut state = ModbusState::new();
+        assert_eq!(AppLayerResult::ok(), state.parse(&req, Direction::ToServer));
+        assert_eq!(state.transactions.len(), 2);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::RdCoils);
+        assert_eq!(
+            msg.data,
+            Data::Read(Read::Request {
+                address: 0x7890,
+                quantity: 0x0013
+            })
+        );
+
+        let tx = &state.transactions[1];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::MultReq {
+                address: 0x0001,
+                quantity: 0x0002,
+                data: vec![0x00, 0x0a, 0x01, 0x02]
+            })
+        );
+
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&resp, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 2);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::RdCoils);
+        assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05])));
+
+        let tx = &state.transactions[1];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::Other {
+                address: 0x0001,
+                data: 0x0002
+            })
+        );
+    }
+
+    #[test]
+    fn exceeded_length_request() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&EXCEEDED_LEN_WR_MULT_REG_REQ, Direction::ToServer)
+        );
+
+        assert_eq!(state.transactions.len(), 1);
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH);
+    }
+
+    #[test]
+    fn invalid_pdu_len_req() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&INVALID_PDU_WR_MULT_REG_REQ, Direction::ToServer)
+        );
+
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrMultRegs);
+        assert_eq!(msg.data, Data::ByteVec(vec![]));
+    }
+
+    #[test]
+    fn mask_write_register_request() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&MASK_WR_REG_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::MaskWrReg);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::Mask {
+                address: 0x0004,
+                and_mask: 0x00f2,
+                or_mask: 0x0025
+            })
+        );
+
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&MASK_WR_REG_RESP, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::MaskWrReg);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::Mask {
+                address: 0x0004,
+                and_mask: 0x00f2,
+                or_mask: 0x0025
+            })
+        );
+    }
+
+    #[test]
+    fn write_single_register_request() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&WR_SINGLE_REG_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrSingleReg);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::Other {
+                address: 0x0001,
+                data: 0x0003
+            })
+        );
+
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&WR_SINGLE_REG_RESP, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrSingleReg);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::Other {
+                address: 0x0001,
+                data: 0x0003
+            })
+        );
+    }
+
+    #[test]
+    fn invalid_mask_write_register_request() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&INVALID_MASK_WR_REG_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::MaskWrReg);
+        assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH);
+        assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x04, 0x00, 0xF2]));
+
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&MASK_WR_REG_RESP, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::MaskWrReg);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::Mask {
+                address: 0x0004,
+                and_mask: 0x00f2,
+                or_mask: 0x0025
+            })
+        );
+    }
+
+    #[test]
+    fn invalid_write_single_register_request() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&INVALID_WR_SINGLE_REG_REQ, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrSingleReg);
+        assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH);
+        assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x01]));
+
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&WR_SINGLE_REG_RESP, Direction::ToClient)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.response.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::WrSingleReg);
+        assert_eq!(
+            msg.data,
+            Data::Write(Write::Other {
+                address: 0x0001,
+                data: 0x0003
+            })
+        );
+    }
+
+    #[test]
+    fn invalid_function_code() {
+        let mut state = ModbusState::new();
+        assert_eq!(
+            AppLayerResult::ok(),
+            state.parse(&INVALID_FUNC_CODE, Direction::ToServer)
+        );
+        assert_eq!(state.transactions.len(), 1);
+
+        let tx = &state.transactions[0];
+        let msg = tx.request.as_ref().unwrap();
+        assert_eq!(msg.function.code, FunctionCode::Unknown);
+        assert_eq!(msg.data, Data::ByteVec(vec![]));
+    }
+}
index 43da199a46db18de8003a4f40972ba4c09aad74a..415e950e38ad3c8b7f9572f2ef9ea5fab2a04ddb 100644 (file)
 #include "suricata-common.h"
 
 #include "util-debug.h"
-#include "util-byte.h"
-#include "util-enum.h"
-#include "util-mem.h"
-#include "util-misc.h"
 
-#include "stream.h"
-#include "stream-tcp.h"
-
-#include "app-layer-protos.h"
 #include "app-layer-parser.h"
 #include "app-layer-modbus.h"
 
-#include "app-layer-detect-proto.h"
-
-#include "conf.h"
-#include "conf-yaml-loader.h"
-#include "decode.h"
-
-SCEnumCharMap modbus_decoder_event_table[ ] = {
-    /* Modbus Application Data Unit messages - ADU Modbus */
-    { "INVALID_PROTOCOL_ID",        MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID    },
-    { "UNSOLICITED_RESPONSE",       MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE   },
-    { "INVALID_LENGTH",             MODBUS_DECODER_EVENT_INVALID_LENGTH         },
-    { "INVALID_UNIT_IDENTIFIER",    MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER},
-
-    /* Modbus Protocol Data Unit messages - PDU Modbus */
-    { "INVALID_FUNCTION_CODE",      MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE  },
-    { "INVALID_VALUE",              MODBUS_DECODER_EVENT_INVALID_VALUE          },
-    { "INVALID_EXCEPTION_CODE",     MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE },
-    { "VALUE_MISMATCH",             MODBUS_DECODER_EVENT_VALUE_MISMATCH         },
-
-    /* Modbus Decoder event */
-    { "FLOODED",                    MODBUS_DECODER_EVENT_FLOODED},
-    { NULL,                         -1 },
-};
-
-/* Modbus Application Data Unit (ADU) length range. */
-#define MODBUS_MIN_ADU_LEN  2
-#define MODBUS_MAX_ADU_LEN  254
-
-/* Modbus Protocol version. */
-#define MODBUS_PROTOCOL_VER 0
-
-/* Modbus Unit Identifier range. */
-#define MODBUS_MIN_INVALID_UNIT_ID  247
-#define MODBUS_MAX_INVALID_UNIT_ID  255
-
-/* Modbus Quantity range. */
-#define MODBUS_MIN_QUANTITY                 0
-#define MODBUS_MAX_QUANTITY_IN_BIT_ACCESS   2000
-#define MODBUS_MAX_QUANTITY_IN_WORD_ACCESS  125
-
-/* Modbus Count range. */
-#define MODBUS_MIN_COUNT    1
-#define MODBUS_MAX_COUNT    250
-
-/* Modbus Function Code. */
-#define MODBUS_FUNC_READCOILS           0x01
-#define MODBUS_FUNC_READDISCINPUTS      0x02
-#define MODBUS_FUNC_READHOLDREGS        0x03
-#define MODBUS_FUNC_READINPUTREGS       0x04
-#define MODBUS_FUNC_WRITESINGLECOIL     0x05
-#define MODBUS_FUNC_WRITESINGLEREG      0x06
-#define MODBUS_FUNC_READEXCSTATUS       0x07
-#define MODBUS_FUNC_DIAGNOSTIC          0x08
-#define MODBUS_FUNC_GETCOMEVTCOUNTER    0x0b
-#define MODBUS_FUNC_GETCOMEVTLOG        0x0c
-#define MODBUS_FUNC_WRITEMULTCOILS      0x0f
-#define MODBUS_FUNC_WRITEMULTREGS       0x10
-#define MODBUS_FUNC_REPORTSERVERID      0x11
-#define MODBUS_FUNC_READFILERECORD      0x14
-#define MODBUS_FUNC_WRITEFILERECORD     0x15
-#define MODBUS_FUNC_MASKWRITEREG        0x16
-#define MODBUS_FUNC_READWRITEMULTREGS   0x17
-#define MODBUS_FUNC_READFIFOQUEUE       0x18
-#define MODBUS_FUNC_ENCAPINTTRANS       0x2b
-#define MODBUS_FUNC_MASK                0x7f
-#define MODBUS_FUNC_ERRORMASK           0x80
-
-/* Modbus Diagnostic functions: Subfunction Code. */
-#define MODBUS_SUBFUNC_QUERY_DATA           0x00
-#define MODBUS_SUBFUNC_RESTART_COM          0x01
-#define MODBUS_SUBFUNC_DIAG_REGS            0x02
-#define MODBUS_SUBFUNC_CHANGE_DELIMITER     0x03
-#define MODBUS_SUBFUNC_LISTEN_MODE          0x04
-#define MODBUS_SUBFUNC_CLEAR_REGS           0x0a
-#define MODBUS_SUBFUNC_BUS_MSG_COUNT        0x0b
-#define MODBUS_SUBFUNC_COM_ERR_COUNT        0x0c
-#define MODBUS_SUBFUNC_EXCEPT_ERR_COUNT     0x0d
-#define MODBUS_SUBFUNC_SERVER_MSG_COUNT     0x0e
-#define MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT  0x0f
-#define MODBUS_SUBFUNC_SERVER_NAK_COUNT     0x10
-#define MODBUS_SUBFUNC_SERVER_BUSY_COUNT    0x11
-#define MODBUS_SUBFUNC_SERVER_CHAR_COUNT    0x12
-#define MODBUS_SUBFUNC_CLEAR_COUNT          0x14
-
-/* Modbus Encapsulated Interface Transport function: MEI type. */
-#define MODBUS_MEI_ENCAPINTTRANS_CAN   0x0d
-#define MODBUS_MEI_ENCAPINTTRANS_READ  0x0e
-
-/* Modbus Exception Codes. */
-#define MODBUS_ERROR_CODE_ILLEGAL_FUNCTION      0x01
-#define MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS  0x02
-#define MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE    0x03
-#define MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE 0x04
-#define MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR   0x08
-
-/* Modbus Application Protocol (MBAP) header. */
-struct ModbusHeader_ {
-    uint16_t     transactionId;
-    uint16_t     protocolId;
-    uint16_t     length;
-    uint8_t      unitId;
-}  __attribute__((__packed__));
-typedef struct ModbusHeader_ ModbusHeader;
-
-/* Modbus Read/Write function and Access Types. */
-#define MODBUS_TYP_WRITE_SINGLE         (MODBUS_TYP_WRITE | MODBUS_TYP_SINGLE)
-#define MODBUS_TYP_WRITE_MULTIPLE       (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
-#define MODBUS_TYP_READ_WRITE_MULTIPLE  (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
-
-/* Macro to convert quantity value (in bit) into count value (in word): count = Ceil(quantity/8) */
-#define CEIL(quantity) (((quantity) + 7)>>3)
-
-/* Modbus Default unreplied Modbus requests are considered a flood */
-#define MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD 500
-
-/* Modbus default stream reassembly depth */
-#define MODBUS_CONFIG_DEFAULT_STREAM_DEPTH 0
-
-static uint32_t request_flood = MODBUS_CONFIG_DEFAULT_REQUEST_FLOOD;
-static uint32_t stream_depth = MODBUS_CONFIG_DEFAULT_STREAM_DEPTH;
-
-static int ModbusStateGetEventInfo(const char *event_name, int *event_id, AppLayerEventType *event_type)
-{
-    *event_id = SCMapEnumNameToValue(event_name, modbus_decoder_event_table);
-
-    if (*event_id == -1) {
-        SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%s\" not present in "
-                   "modbus's enum map table.",  event_name);
-        /* yes this is fatal */
-        return -1;
-    }
-
-    *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
-
-    return 0;
-}
-
-static int ModbusStateGetEventInfoById(int event_id, const char **event_name,
-                                       AppLayerEventType *event_type)
-{
-    *event_name = SCMapEnumValueToName(event_id, modbus_decoder_event_table);
-
-    if (*event_name == NULL) {
-        SCLogError(SC_ERR_INVALID_ENUM_MAP, "event \"%d\" not present in "
-                   "modbus's enum map table.",  event_id);
-        /* yes this is fatal */
-        return -1;
-    }
-
-    *event_type = APP_LAYER_EVENT_TYPE_TRANSACTION;
-
-    return 0;
-}
-
-static void ModbusSetEvent(ModbusState *modbus, uint8_t e)
-{
-    if (modbus && modbus->curr) {
-        SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events);
-        AppLayerDecoderEventsSetEventRaw(&modbus->curr->decoder_events, e);
-        SCLogDebug("modbus->curr->decoder_events %p", modbus->curr->decoder_events);
-        modbus->events++;
-    } else
-        SCLogDebug("couldn't set event %u", e);
-}
-
-static AppLayerDecoderEvents *ModbusGetEvents(void *tx)
-{
-    return ((ModbusTransaction *)tx)->decoder_events;
-}
-
-static int ModbusGetAlstateProgress(void *modbus_tx, uint8_t direction)
-{
-    ModbusTransaction   *tx     = (ModbusTransaction *) modbus_tx;
-    ModbusState         *modbus = tx->modbus;
-
-    if (tx->replied == 1)
-        return 1;
-
-    /* Check flood limit */
-    if ((modbus->givenup == 1)  &&
-        ((modbus->transaction_max - tx->tx_num) > request_flood))
-        return 1;
-
-    return 0;
-}
-
-static void *ModbusGetTx(void *alstate, uint64_t tx_id)
-{
-    ModbusState         *modbus = (ModbusState *) alstate;
-    ModbusTransaction   *tx = NULL;
-
-    if (modbus->curr && modbus->curr->tx_num == tx_id + 1)
-        return modbus->curr;
-
-    TAILQ_FOREACH(tx, &modbus->tx_list, next) {
-        SCLogDebug("tx->tx_num %"PRIu64", tx_id %"PRIu64, tx->tx_num, (tx_id+1));
-        if (tx->tx_num != (tx_id+1))
-            continue;
-
-        SCLogDebug("returning tx %p", tx);
-        return tx;
-    }
-
-    return NULL;
-}
-
-static AppLayerTxData *ModbusGetTxData(void *vtx)
-{
-    ModbusTransaction *tx = (ModbusTransaction *)vtx;
-    return &tx->tx_data;
-}
-
-static uint64_t ModbusGetTxCnt(void *alstate)
-{
-    return ((uint64_t) ((ModbusState *) alstate)->transaction_max);
-}
-
-/** \internal
- *  \brief Find the Modbus Transaction in the state based on Transaction ID.
- *
- *  \param  modbus          Pointer to Modbus state structure
- *  \param  transactionId   Transaction ID of the transaction
- *
- *  \retval tx or NULL      if not found
- */
-static ModbusTransaction *ModbusTxFindByTransaction(const ModbusState   *modbus,
-                                                    const uint16_t      transactionId)
-{
-    ModbusTransaction *tx = NULL;
-
-    if (modbus->curr == NULL)
-        return NULL;
-
-    /* fast path */
-    if ((modbus->curr->transactionId == transactionId)  &&
-        !(modbus->curr->replied)) {
-        return modbus->curr;
-    /* slow path, iterate list */
-    } else {
-        TAILQ_FOREACH(tx, &modbus->tx_list, next) {
-            if ((tx->transactionId == transactionId)    &&
-                !(modbus->curr->replied))
-                return tx;
-        }
-    }
-    /* not found */
-    return NULL;
-}
-
-/** \internal
- *  \brief Allocate a Modbus Transaction and
- *          add it into Transaction list of Modbus State
- *
- *  \param  modbus Pointer to Modbus state structure
- *
- *  \retval Pointer to Transaction or NULL pointer
- */
-static ModbusTransaction *ModbusTxAlloc(ModbusState *modbus) {
-    ModbusTransaction *tx;
-
-    /* Check flood limit */
-    if ((request_flood != 0) && (modbus->unreplied_cnt >= request_flood)) {
-        ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_FLOODED);
-        modbus->givenup = 1;
-        return NULL;
-    }
-
-    tx = (ModbusTransaction *) SCCalloc(1, sizeof(ModbusTransaction));
-    if (unlikely(tx == NULL))
-        return NULL;
-
-    modbus->transaction_max++;
-    modbus->unreplied_cnt++;
-
-    modbus->curr = tx;
-
-    SCLogDebug("modbus->transaction_max updated to %"PRIu64, modbus->transaction_max);
-
-    TAILQ_INSERT_TAIL(&modbus->tx_list, tx, next);
-
-    tx->modbus  = modbus;
-    tx->tx_num  = modbus->transaction_max;
-
-    return tx;
-}
-
-/** \internal
- *  \brief Free a Modbus Transaction
- *
- *  \retval Pointer to Transaction or NULL pointer
- */
-static void ModbusTxFree(ModbusTransaction *tx) {
-    SCEnter();
-    if (tx->data != NULL)
-        SCFree(tx->data);
-
-    AppLayerDecoderEventsFreeEvents(&tx->decoder_events);
-
-    if (tx->de_state != NULL)
-        DetectEngineStateFree(tx->de_state);
-
-    SCFree(tx);
-    SCReturn;
-}
+void ModbusParserRegisterTests(void);
 
 /**
- *  \brief Modbus transaction cleanup callback
- */
-static void ModbusStateTxFree(void *state, uint64_t tx_id)
-{
-    SCEnter();
-    ModbusState         *modbus = (ModbusState *) state;
-    ModbusTransaction   *tx = NULL, *ttx;
-
-    SCLogDebug("state %p, id %"PRIu64, modbus, tx_id);
-
-    TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) {
-        SCLogDebug("tx %p tx->tx_num %"PRIu64", tx_id %"PRIu64, tx, tx->tx_num, (tx_id+1));
-
-        if (tx->tx_num != (tx_id+1))
-            continue;
-
-        if (tx == modbus->curr)
-            modbus->curr = NULL;
-
-        if (tx->decoder_events != NULL) {
-            if (tx->decoder_events->cnt <= modbus->events)
-                modbus->events -= tx->decoder_events->cnt;
-            else
-                modbus->events = 0;
-        }
-
-        modbus->unreplied_cnt--;
-
-        /* Check flood limit */
-        if ((modbus->givenup == 1)                  &&
-            (request_flood != 0)                    &&
-            (modbus->unreplied_cnt < request_flood) )
-            modbus->givenup = 0;
-
-        TAILQ_REMOVE(&modbus->tx_list, tx, next);
-        ModbusTxFree(tx);
-        break;
-    }
-    SCReturn;
-}
-
-/** \internal
- *  \brief Extract 8bits data from pointer the received input data
- *
- *  \param  res                    Pointer to the result
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- *  \param  offset      Offset of the received input data pointer
- */
-static int ModbusExtractUint8(ModbusState   *modbus,
-                              uint8_t       *res,
-                              const uint8_t *input,
-                              uint32_t      input_len,
-                              uint16_t      *offset) {
-    SCEnter();
-    if (input_len < (uint32_t) (*offset + sizeof(uint8_t))) {
-        ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
-        SCReturnInt(-1);
-    }
-
-    *res     = *(input + *offset);
-    *offset += sizeof(uint8_t);
-    SCReturnInt(0);
-}
-
-/** \internal
- *  \brief Extract 16bits data from pointer the received input data
- *
- *  \param  res                    Pointer to the result
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- *  \param  offset      Offset of the received input data pointer
- */
-static int ModbusExtractUint16(ModbusState  *modbus,
-                               uint16_t     *res,
-                               const uint8_t *input,
-                               uint32_t     input_len,
-                               uint16_t     *offset) {
-    SCEnter();
-    if (input_len < (uint32_t) (*offset + sizeof(uint16_t))) {
-        ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
-        SCReturnInt(-1);
-    }
-
-    ByteExtractUint16(res, BYTE_BIG_ENDIAN, sizeof(uint16_t), (const uint8_t *) (input + *offset));
-    *offset += sizeof(uint16_t);
-    SCReturnInt(0);
-}
-
-/** \internal
- *  \brief Check length field in Modbus header according to code function
- *
- *  \param  modbus  Pointer to Modbus state structure
- *  \param  length  Length field in Modbus Header
- *  \param  len                Length according to code functio
- */
-static int ModbusCheckHeaderLength(ModbusState *modbus,
-                                   uint16_t    length,
-                                   uint16_t    len) {
-    SCEnter();
-    if (length != len) {
-        ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
-        SCReturnInt(-1);
-    }
-    SCReturnInt(0);
-}
-
-/** \internal
- *  \brief Check Modbus header
- *
- *  \param  tx      Pointer to Modbus Transaction structure
- *  \param  modbus  Pointer to Modbus state structure
- *  \param  header  Pointer to Modbus header state in which the value to be stored
- */
-static void ModbusCheckHeader(ModbusState       *modbus,
-                              ModbusHeader      *header)
-{
-    SCEnter();
-    /* MODBUS protocol is identified by the value 0. */
-    if (header->protocolId != MODBUS_PROTOCOL_VER)
-        ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID);
-
-    /* Check Length field that is a byte count of the following fields */
-    if ((header->length < MODBUS_MIN_ADU_LEN)   ||
-        (header->length > MODBUS_MAX_ADU_LEN)   )
-        ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_LENGTH);
-
-    /* Check Unit Identifier field that is not in invalid range */
-    if ((header->unitId > MODBUS_MIN_INVALID_UNIT_ID)   &&
-        (header->unitId < MODBUS_MAX_INVALID_UNIT_ID)   )
-        ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER);
-
-    SCReturn;
-}
-
-/** \internal
- *  \brief Parse Exception Response and verify protocol compliance.
- *
- *  \param  tx          Pointer to Modbus Transaction structure
- *  \param  modbus      Pointer to Modbus state structure
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- *  \param  offset      Offset of the received input data pointer
- */
-static void ModbusExceptionResponse(ModbusTransaction   *tx,
-                                    ModbusState         *modbus,
-                                    const uint8_t       *input,
-                                    uint32_t            input_len,
-                                    uint16_t            *offset)
-{
-    SCEnter();
-    uint8_t exception = 0;
-
-    /* Exception code (1 byte) */
-    if (ModbusExtractUint8(modbus, &exception, input, input_len, offset))
-        SCReturn;
-
-    switch (exception) {
-        case MODBUS_ERROR_CODE_ILLEGAL_FUNCTION:
-        case MODBUS_ERROR_CODE_SERVER_DEVICE_FAILURE:
-            break;
-        case MODBUS_ERROR_CODE_ILLEGAL_DATA_VALUE:
-            if (tx->function == MODBUS_FUNC_DIAGNOSTIC) {
-                break;
-            }
-            /* Fallthrough */
-        case MODBUS_ERROR_CODE_ILLEGAL_DATA_ADDRESS:
-            if (    (tx->type & MODBUS_TYP_ACCESS_FUNCTION_MASK)    ||
-                    (tx->function == MODBUS_FUNC_READFIFOQUEUE)     ||
-                    (tx->function == MODBUS_FUNC_ENCAPINTTRANS)) {
-                break;
-            }
-            /* Fallthrough */
-        case MODBUS_ERROR_CODE_MEMORY_PARITY_ERROR:
-            if (    (tx->function == MODBUS_FUNC_READFILERECORD)     ||
-                    (tx->function == MODBUS_FUNC_WRITEFILERECORD)    ) {
-                break;
-            }
-            /* Fallthrough */
-        default:
-            ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE);
-            break;
-    }
-
-    SCReturn;
-}
-
-/** \internal
- *  \brief Parse Read data Request, complete Transaction structure
- *          and verify protocol compliance.
- *
- *  \param  tx          Pointer to Modbus Transaction structure
- *  \param  modbus      Pointer to Modbus state structure
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- *  \param  offset      Offset of the received input data pointer
- */
-static void ModbusParseReadRequest(ModbusTransaction   *tx,
-                                   ModbusState         *modbus,
-                                   const uint8_t       *input,
-                                   uint32_t            input_len,
-                                   uint16_t            *offset)
-{
-    SCEnter();
-    uint16_t    quantity;
-    uint8_t     type = tx->type;
-
-    /* Starting Address (2 bytes) */
-    if (ModbusExtractUint16(modbus, &(tx->read.address), input, input_len, offset))
-        goto end;
-
-    /* Quantity (2 bytes) */
-    if (ModbusExtractUint16(modbus, &(tx->read.quantity), input, input_len, offset))
-        goto end;
-    quantity = tx->read.quantity;
-
-    /* Check Quantity range */
-    if (type & MODBUS_TYP_BIT_ACCESS_MASK) {
-        if ((quantity == MODBUS_MIN_QUANTITY) ||
-            (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS))
-            goto error;
-    } else {
-        if ((quantity == MODBUS_MIN_QUANTITY) ||
-            (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS))
-            goto error;
-    }
-
-    if (~type & MODBUS_TYP_WRITE)
-        /* Except from Read/Write Multiple Registers function (code 23)     */
-        /* The length of all Read Data function requests is 6 bytes         */
-        /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4 */
-        ModbusCheckHeaderLength(modbus, tx->length, 6);
-
-    goto end;
-
-error:
-    ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE);
-end:
-    SCReturn;
-}
-
-/** \internal
- *  \brief Parse Read data Response and verify protocol compliance
- *
- *  \param  tx          Pointer to Modbus Transaction structure
- *  \param  modbus      Pointer to Modbus state structure
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- *  \param  offset      Offset of the received input data pointer
- */
-static void ModbusParseReadResponse(ModbusTransaction   *tx,
-                                    ModbusState         *modbus,
-                                    const uint8_t       *input,
-                                    uint32_t            input_len,
-                                    uint16_t            *offset)
-{
-    SCEnter();
-    uint8_t count = 0;
-
-    /* Count (1 bytes) */
-    if (ModbusExtractUint8(modbus, &count, input, input_len, offset))
-        goto end;
-
-    /* Check Count range and value according to the request */
-    if ((tx->type) & MODBUS_TYP_BIT_ACCESS_MASK) {
-        if (    (count < MODBUS_MIN_COUNT)          ||
-                (count > MODBUS_MAX_COUNT)          ||
-                (count != CEIL(tx->read.quantity)))
-            goto error;
-    } else {
-        if (    (count == MODBUS_MIN_COUNT)         ||
-                (count > MODBUS_MAX_COUNT)          ||
-                (count != (2 * (tx->read.quantity))))
-            goto error;
-    }
-
-    /* Except from Read/Write Multiple Registers function (code 23)         */
-    /* The length of all Read Data function responses is (3 bytes + count)  */
-    /* Modbus Application Protocol Specification V1.1b3 from 6.1 to 6.4     */
-    ModbusCheckHeaderLength(modbus, tx->length, 3 + count);
-    goto end;
-
-error:
-    ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
-end:
-    SCReturn;
-}
-
-/** \internal
- *  \brief Parse Write data Request, complete Transaction structure
- *          and verify protocol compliance.
- *
- *  \param  tx          Pointer to Modbus Transaction structure
- *  \param  modbus      Pointer to Modbus state structure
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- *  \param  offset      Offset of the received input data pointer
- *
- *  \retval On success returns 0 or on failure returns -1.
- */
-static int ModbusParseWriteRequest(ModbusTransaction   *tx,
-                                   ModbusState         *modbus,
-                                   const uint8_t       *input,
-                                   uint32_t            input_len,
-                                   uint16_t            *offset)
-{
-    SCEnter();
-    uint16_t    quantity = 1, word = 0;
-    uint8_t     byte = 0, count = 1, type = tx->type;
-
-    int i = 0;
-
-    /* Starting/Output/Register Address (2 bytes) */
-    if (ModbusExtractUint16(modbus, &(tx->write.address), input, input_len, offset))
-        goto end;
-
-    if (type & MODBUS_TYP_SINGLE) {
-        /* The length of Write Single Coil (code 5) and                 */
-        /* Write Single Register (code 6) requests is 6 bytes           */
-        /* Modbus Application Protocol Specification V1.1b3 6.5 and 6.6 */
-        if (ModbusCheckHeaderLength(modbus, tx->length, 6))
-            goto end;
-    } else if (type & MODBUS_TYP_MULTIPLE) {
-        /* Quantity (2 bytes) */
-        if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset))
-            goto end;
-        tx->write.quantity = quantity;
-
-        /* Count (1 bytes) */
-        if (ModbusExtractUint8(modbus, &count, input, input_len, offset))
-            goto end;
-        tx->write.count = count;
-
-        if (type & MODBUS_TYP_BIT_ACCESS_MASK) {
-            /* Check Quantity range and conversion in byte (count) */
-            if ((quantity == MODBUS_MIN_QUANTITY)               ||
-                (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS)  ||
-                (quantity != CEIL(count)))
-                goto error;
-
-            /* The length of Write Multiple Coils (code 15) request is (7 + count)  */
-            /* Modbus Application Protocol Specification V1.1b3 6.11                */
-            if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count))
-                goto end;
-        } else {
-            /* Check Quantity range and conversion in byte (count) */
-            if ((quantity == MODBUS_MIN_QUANTITY)               ||
-                (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS) ||
-                (count != (2 * quantity)))
-                goto error;
-
-            if (type & MODBUS_TYP_READ) {
-                /* The length of Read/Write Multiple Registers function (code 23)   */
-                /* request is (11 bytes + count)                                    */
-                /* Modbus Application Protocol Specification V1.1b3 6.17            */
-                if (ModbusCheckHeaderLength(modbus, tx->length, 11 + count))
-                    goto end;
-            } else {
-                /* The length of Write Multiple Coils (code 15) and                             */
-                /* Write Multiple Registers (code 16) functions requests is (7 bytes + count)   */
-                /* Modbus Application Protocol Specification V1.1b3 from 6.11 and 6.12          */
-                if (ModbusCheckHeaderLength(modbus, tx->length, 7 + count))
-                    goto end;
-            }
-        }
-    } else {
-        /* Mask Write Register function (And_Mask and Or_Mask) */
-        quantity = 2;
-
-        /* The length of Mask Write Register (code 22) function request is 8    */
-        /* Modbus Application Protocol Specification V1.1b3 6.16                */
-        if (ModbusCheckHeaderLength(modbus, tx->length, 8))
-            goto end;
-    }
-
-    if (type & MODBUS_TYP_COILS) {
-        /* Output value (data block) unit is count */
-        tx->data = (uint16_t *) SCCalloc(1, count * sizeof(uint16_t));
-        if (unlikely(tx->data == NULL))
-            SCReturnInt(-1);
-
-        if (type & MODBUS_TYP_SINGLE) {
-            /* Outputs value (2 bytes) */
-            if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
-                goto end;
-            tx->data[i] = word;
-
-            if ((word != 0x00) && (word != 0xFF00))
-                goto error;
-        } else {
-            for (i = 0; i < count; i++) {
-                /* Outputs value (1 byte) */
-                if (ModbusExtractUint8(modbus, &byte, input, input_len, offset))
-                    goto end;
-                tx->data[i] = (uint16_t) byte;
-            }
-        }
-    } else {
-        /* Registers value (data block) unit is quantity */
-        tx->data = (uint16_t *) SCCalloc(1, quantity * sizeof(uint16_t));
-        if (unlikely(tx->data == NULL))
-            SCReturnInt(-1);
-
-        for (i = 0; i < quantity; i++) {
-            /* Outputs/Registers value (2 bytes) */
-            if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
-                goto end;
-            tx->data[i] = word;
-        }
-    }
-    goto end;
-
-error:
-    ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE);
-end:
-    SCReturnInt(0);
-}
-
-/** \internal
- *  \brief Parse Write data Response and verify protocol compliance
- *
- *  \param  tx          Pointer to Modbus Transaction structure
- *  \param  modbus      Pointer to Modbus state structure
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- *  \param  offset      Offset of the received input data pointer
- */
-static void ModbusParseWriteResponse(ModbusTransaction   *tx,
-                                     ModbusState         *modbus,
-                                     const uint8_t       *input,
-                                     uint32_t            input_len,
-                                     uint16_t            *offset)
-{
-    SCEnter();
-    uint16_t    address = 0, quantity = 0, word = 0;
-    uint8_t     type = tx->type;
-
-    /* Starting Address (2 bytes) */
-    if (ModbusExtractUint16(modbus, &address, input, input_len, offset))
-        goto end;
-
-    if (address != tx->write.address)
-        goto error;
-
-    if (type & MODBUS_TYP_SINGLE) {
-        /* Check if Outputs/Registers value has been stored */
-        if (tx->data != NULL)
-        {
-            /* Outputs/Registers value (2 bytes) */
-            if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
-                goto end;
-
-            /* Check with Outputs/Registers from request */
-            if (word != tx->data[0])
-                goto error;
-        }
-    } else if (type & MODBUS_TYP_MULTIPLE) {
-        /* Quantity (2 bytes) */
-        if (ModbusExtractUint16(modbus, &quantity, input, input_len, offset))
-            goto end;
-
-        /* Check Quantity range */
-        if (type & MODBUS_TYP_BIT_ACCESS_MASK) {
-            if ((quantity == MODBUS_MIN_QUANTITY) ||
-                (quantity > MODBUS_MAX_QUANTITY_IN_WORD_ACCESS))
-                goto error;
-        } else {
-            if ((quantity == MODBUS_MIN_QUANTITY) ||
-                (quantity > MODBUS_MAX_QUANTITY_IN_BIT_ACCESS))
-                goto error;
-        }
-
-        /* Check Quantity value according to the request */
-        if (quantity != tx->write.quantity)
-            goto error;
-    } else {
-        /* Check if And_Mask and Or_Mask values have been stored */
-        if (tx->data != NULL)
-        {
-            /* And_Mask value (2 bytes) */
-            if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
-                goto end;
-
-            /* Check And_Mask value according to the request */
-            if (word != tx->data[0])
-                goto error;
-
-            /* And_Or_Mask value (2 bytes) */
-            if (ModbusExtractUint16(modbus, &word, input, input_len, offset))
-                goto end;
-
-            /* Check Or_Mask value according to the request */
-            if (word != tx->data[1])
-                goto error;
-        }
-
-        /* The length of Mask Write Register (code 22) function response is 8   */
-        /* Modbus Application Protocol Specification V1.1b3 6.16                */
-        ModbusCheckHeaderLength(modbus, tx->length, 8);
-        goto end;
-    }
-
-    /* Except from Mask Write Register (code 22)                                        */
-    /* The length of all Write Data function responses is 6                             */
-    /* Modbus Application Protocol Specification V1.1b3 6.5, 6.6, 6.11, 6.12 and 6.17   */
-    ModbusCheckHeaderLength(modbus, tx->length, 6);
-    goto end;
-
-error:
-    ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
-end:
-    SCReturn;
-}
-
-/** \internal
- *  \brief Parse Diagnostic Request, complete Transaction
- *          structure (Category) and verify protocol compliance.
- *
- *  \param  tx          Pointer to Modbus Transaction structure
- *  \param  modbus      Pointer to Modbus state structure
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- *  \param  offset      Offset of the received input data pointer
- *
- *  \retval Reserved category function returns 1 otherwise returns 0.
- */
-static int ModbusParseDiagnosticRequest(ModbusTransaction   *tx,
-                                        ModbusState         *modbus,
-                                        const uint8_t       *input,
-                                        uint32_t            input_len,
-                                        uint16_t            *offset)
-{
-    SCEnter();
-    uint16_t data = 0;
-
-    /* Sub-function (2 bytes) */
-    if (ModbusExtractUint16(modbus, &(tx->subFunction), input, input_len, offset))
-        goto end;
-
-    /* Data (2 bytes) */
-    if (ModbusExtractUint16(modbus, &data, input, input_len, offset))
-        goto end;
-
-    if (tx->subFunction != MODBUS_SUBFUNC_QUERY_DATA) {
-        switch (tx->subFunction) {
-            case MODBUS_SUBFUNC_RESTART_COM:
-                if ((data != 0x00) && (data != 0xFF00))
-                    goto error;
-                break;
-
-            case MODBUS_SUBFUNC_CHANGE_DELIMITER:
-                if ((data & 0xFF) != 0x00)
-                    goto error;
-                break;
-
-            case MODBUS_SUBFUNC_LISTEN_MODE:
-                /* No answer is expected then mark tx as completed. */
-                tx->replied = 1;
-                /* Fallthrough */
-            case MODBUS_SUBFUNC_DIAG_REGS:
-            case MODBUS_SUBFUNC_CLEAR_REGS:
-            case MODBUS_SUBFUNC_BUS_MSG_COUNT:
-            case MODBUS_SUBFUNC_COM_ERR_COUNT:
-            case MODBUS_SUBFUNC_EXCEPT_ERR_COUNT:
-            case MODBUS_SUBFUNC_SERVER_MSG_COUNT:
-            case MODBUS_SUBFUNC_SERVER_NO_RSP_COUNT:
-            case MODBUS_SUBFUNC_SERVER_NAK_COUNT:
-            case MODBUS_SUBFUNC_SERVER_BUSY_COUNT:
-            case MODBUS_SUBFUNC_SERVER_CHAR_COUNT:
-            case MODBUS_SUBFUNC_CLEAR_COUNT:
-                if (data != 0x00)
-                    goto error;
-                break;
-
-            default:
-                /* Set function code category */
-                tx->category = MODBUS_CAT_RESERVED;
-                SCReturnInt(1);
-        }
-
-        /* The length of all Diagnostic Requests is 6           */
-        /* Modbus Application Protocol Specification V1.1b3 6.8 */
-        ModbusCheckHeaderLength(modbus, tx->length, 6);
-    }
-
-    goto end;
-
-error:
-    ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_VALUE);
-end:
-    SCReturnInt(0);
-}
-
-/* Modbus Function Code Categories structure. */
-typedef struct ModbusFunctionCodeRange_ {
-    uint8_t        function;
-    uint8_t        category;
-} ModbusFunctionCodeRange;
-
-/* Modbus Function Code Categories table. */
-static ModbusFunctionCodeRange modbusFunctionCodeRanges[] = {
-        { 0,    MODBUS_CAT_PUBLIC_UNASSIGNED},
-        { 9,    MODBUS_CAT_RESERVED         },
-        { 15,   MODBUS_CAT_PUBLIC_UNASSIGNED},
-        { 41,   MODBUS_CAT_RESERVED         },
-        { 43,   MODBUS_CAT_PUBLIC_UNASSIGNED},
-        { 65,   MODBUS_CAT_USER_DEFINED     },
-        { 73,   MODBUS_CAT_PUBLIC_UNASSIGNED},
-        { 90,   MODBUS_CAT_RESERVED         },
-        { 92,   MODBUS_CAT_PUBLIC_UNASSIGNED},
-        { 100,  MODBUS_CAT_USER_DEFINED     },
-        { 111,  MODBUS_CAT_PUBLIC_UNASSIGNED},
-        { 125,  MODBUS_CAT_RESERVED         },
-        { 128,  MODBUS_CAT_NONE             }
-};
-
-/** \internal
- *  \brief Parse the Modbus Protocol Data Unit (PDU) Request
- *
- *  \param  tx          Pointer to Modbus Transaction structure
- *  \param  ModbusPdu   Pointer the Modbus PDU state in which the value to be stored
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- */
-static void ModbusParseRequestPDU(ModbusTransaction *tx,
-                                  ModbusState       *modbus,
-                                  const uint8_t     *input,
-                                  uint32_t          input_len)
-{
-    SCEnter();
-    uint16_t    offset = (uint16_t) sizeof(ModbusHeader);
-    uint8_t     count = 0;
-
-    int i = 0;
-
-    /* Standard function codes used on MODBUS application layer protocol (1 byte) */
-    if (ModbusExtractUint8(modbus, &(tx->function), input, input_len, &offset))
-        goto end;
-
-    /* Set default function code category */
-    tx->category = MODBUS_CAT_NONE;
-
-    /* Set default function primary table */
-    tx->type = MODBUS_TYP_NONE;
-
-    switch (tx->function) {
-        case MODBUS_FUNC_NONE:
-            ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE);
-            break;
-
-        case MODBUS_FUNC_READCOILS:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_READ);
-            break;
-
-        case MODBUS_FUNC_READDISCINPUTS:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_DISCRETES | MODBUS_TYP_READ);
-            break;
-
-        case MODBUS_FUNC_READHOLDREGS:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ);
-            break;
-
-        case MODBUS_FUNC_READINPUTREGS:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_INPUT | MODBUS_TYP_READ);
-            break;
-
-        case MODBUS_FUNC_WRITESINGLECOIL:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_SINGLE);
-            break;
-
-        case MODBUS_FUNC_WRITESINGLEREG:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_SINGLE);
-            break;
-
-        case MODBUS_FUNC_WRITEMULTCOILS:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_COILS | MODBUS_TYP_WRITE_MULTIPLE);
-            break;
-
-        case MODBUS_FUNC_WRITEMULTREGS:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE_MULTIPLE);
-            break;
-
-        case MODBUS_FUNC_MASKWRITEREG:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_WRITE);
-            break;
-
-        case MODBUS_FUNC_READWRITEMULTREGS:
-            /* Set function type */
-            tx->type = (MODBUS_TYP_HOLDING | MODBUS_TYP_READ_WRITE_MULTIPLE);
-            break;
-
-        case MODBUS_FUNC_READFILERECORD:
-        case MODBUS_FUNC_WRITEFILERECORD:
-            /* Count/length (1 bytes) */
-            if (ModbusExtractUint8(modbus, &count, input, input_len, &offset))
-                goto end;
-
-            /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15   */
-            ModbusCheckHeaderLength(modbus, tx->length, 2 + count);
-            break;
-
-        case MODBUS_FUNC_DIAGNOSTIC:
-            if(ModbusParseDiagnosticRequest(tx, modbus, input, input_len, &offset))
-                goto end;
-            break;
-
-        case MODBUS_FUNC_READEXCSTATUS:
-        case MODBUS_FUNC_GETCOMEVTCOUNTER:
-        case MODBUS_FUNC_GETCOMEVTLOG:
-        case MODBUS_FUNC_REPORTSERVERID:
-            /* Modbus Application Protocol Specification V1.1b3 6.7, 6.9, 6.10 and 6.13 */
-            ModbusCheckHeaderLength(modbus, tx->length, 2);
-            break;
-
-        case MODBUS_FUNC_READFIFOQUEUE:
-            /* Modbus Application Protocol Specification V1.1b3 6.18 */
-            ModbusCheckHeaderLength(modbus, tx->length, 4);
-            break;
-
-        case MODBUS_FUNC_ENCAPINTTRANS:
-            /* MEI type (1 byte) */
-           if (ModbusExtractUint8(modbus, &(tx->mei), input, input_len, &offset))
-               goto end;
-
-            if (tx->mei == MODBUS_MEI_ENCAPINTTRANS_READ) {
-                /* Modbus Application Protocol Specification V1.1b3 6.21 */
-                ModbusCheckHeaderLength(modbus, tx->length, 5);
-            } else if (tx->mei != MODBUS_MEI_ENCAPINTTRANS_CAN) {
-                /* Set function code category */
-                tx->category = MODBUS_CAT_RESERVED;
-                goto end;
-            }
-            break;
-
-        default:
-            /* Check if request is error. */
-            if (tx->function & MODBUS_FUNC_ERRORMASK) {
-                ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE);
-                goto end;
-            }
-
-            /* Get and store function code category */
-            for (i = 0; modbusFunctionCodeRanges[i].category != MODBUS_CAT_NONE; i++) {
-                if (tx->function <= modbusFunctionCodeRanges[i].function)
-                    break;
-                tx->category = modbusFunctionCodeRanges[i].category;
-            }
-            goto end;
-    }
-
-    /* Set function code category */
-    tx->category = MODBUS_CAT_PUBLIC_ASSIGNED;
-
-    if (tx->type & MODBUS_TYP_READ)
-        ModbusParseReadRequest(tx, modbus, input, input_len, &offset);
-
-    if (tx->type & MODBUS_TYP_WRITE)
-        ModbusParseWriteRequest(tx, modbus, input, input_len, &offset);
-
-end:
-    SCReturn;
-}
-
-/** \internal
- *  \brief Parse the Modbus Protocol Data Unit (PDU) Response
- *
- *  \param  tx          Pointer to Modbus Transaction structure
- *  \param  modbus      Pointer the Modbus PDU state in which the value to be stored
- *  \param  input       Pointer the received input data
- *  \param  input_len   Length of the received input data
- *  \param  offset      Offset of the received input data pointer
- */
-static void ModbusParseResponsePDU(ModbusTransaction    *tx,
-                                   ModbusState          *modbus,
-                                   const uint8_t        *input,
-                                   uint32_t             input_len)
-{
-    SCEnter();
-    uint16_t    offset = (uint16_t) sizeof(ModbusHeader);
-    uint8_t count = 0, function = 0, mei = 0;
-    uint8_t error = false;
-
-    /* Standard function codes used on MODBUS application layer protocol (1 byte) */
-    if (ModbusExtractUint8(modbus, &function, input, input_len, &offset))
-        goto end;
-
-    /* Check if response is error */
-    if(function & MODBUS_FUNC_ERRORMASK) {
-        function &= MODBUS_FUNC_MASK;
-        error = true;
-    }
-
-    if (tx->category == MODBUS_CAT_PUBLIC_ASSIGNED) {
-        /* Check if response is error. */
-        if (error) {
-            ModbusExceptionResponse(tx, modbus, input, input_len, &offset);
-        } else {
-            switch(function) {
-                case MODBUS_FUNC_READEXCSTATUS:
-                    /* Modbus Application Protocol Specification V1.1b3 6.7 */
-                    ModbusCheckHeaderLength(modbus, tx->length, 3);
-                    goto end;
-
-                case MODBUS_FUNC_GETCOMEVTCOUNTER:
-                    /* Modbus Application Protocol Specification V1.1b3 6.9 */
-                    ModbusCheckHeaderLength(modbus, tx->length, 6);
-                    goto end;
-
-                case MODBUS_FUNC_READFILERECORD:
-                case MODBUS_FUNC_WRITEFILERECORD:
-                    /* Count/length (1 bytes) */
-                    if (ModbusExtractUint8(modbus, &count, input, input_len, &offset))
-                        goto end;
-
-                    /* Modbus Application Protocol Specification V1.1b3 6.14 and 6.15 */
-                    ModbusCheckHeaderLength(modbus, tx->length, 2 + count);
-                    goto end;
-
-                case MODBUS_FUNC_ENCAPINTTRANS:
-                    /* MEI type (1 byte) */
-                    if (ModbusExtractUint8(modbus, &mei, input, input_len, &offset))
-                        goto end;
-
-                    if (mei != tx->mei)
-                        ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_VALUE_MISMATCH);
-                    goto end;
-            }
-
-            if (tx->type & MODBUS_TYP_READ)
-                ModbusParseReadResponse(tx, modbus, input, input_len, &offset);
-            /* Read/Write response contents none write response part */
-            else if (tx->type & MODBUS_TYP_WRITE)
-                ModbusParseWriteResponse(tx, modbus, input, input_len, &offset);
-        }
-    }
-
-end:
-    SCReturn;
-}
-
-/** \internal
- *  \brief Parse the Modbus Application Protocol (MBAP) header
- *
- *  \param  header  Pointer the Modbus header state in which the value to be stored
- *  \param  input   Pointer the received input data
- */
-static int ModbusParseHeader(ModbusState   *modbus,
-                             ModbusHeader  *header,
-                             const uint8_t *input,
-                             uint32_t      input_len)
-{
-    SCEnter();
-    uint16_t offset = 0;
-
-    int r = 0;
-
-    /* can't pass the header fields directly due to alignment (Bug 2088) */
-    uint16_t transaction_id = 0;
-    uint16_t protocol_id = 0;
-    uint16_t length = 0;
-    uint8_t unit_id = 0;
-
-    /* Transaction Identifier (2 bytes) */
-    r = ModbusExtractUint16(modbus, &transaction_id, input, input_len, &offset);
-    /* Protocol Identifier (2 bytes) */
-    r |= ModbusExtractUint16(modbus, &protocol_id, input, input_len, &offset);
-    /* Length (2 bytes) */
-    r |= ModbusExtractUint16(modbus, &length, input, input_len, &offset);
-    /* Unit Identifier (1 byte) */
-    r |= ModbusExtractUint8(modbus, &unit_id, input, input_len, &offset);
-
-    if (r != 0) {
-        SCReturnInt(-1);
-    }
-    header->transactionId = transaction_id;
-    header->protocolId = protocol_id;
-    header->length = length;
-    header->unitId = unit_id;
-
-    SCReturnInt(0);
-}
-
-/** \internal
- *
- * \brief This function is called to retrieve a Modbus Request
- *
- * \param state     Modbus state structure for the parser
- * \param input     Input line of the command
- * \param input_len Length of the request
- *
- * \retval AppLayerResult APP_LAYER_OK or APP_LAYER_ERROR
- */
-static AppLayerResult ModbusParseRequest(Flow       *f,
-                              void                  *state,
-                              AppLayerParserState   *pstate,
-                              const uint8_t         *input,
-                              uint32_t              input_len,
-                              void                  *local_data,
-                              const uint8_t         flags)
-{
-    SCEnter();
-    ModbusState         *modbus = (ModbusState *) state;
-    ModbusTransaction   *tx;
-    ModbusHeader        header;
-
-    if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS)) {
-        SCReturnStruct(APP_LAYER_OK);
-    } else if (input == NULL || input_len == 0) {
-        SCReturnStruct(APP_LAYER_ERROR);
-    }
-
-    while (input_len > 0) {
-        uint32_t    adu_len = input_len;
-        const uint8_t     *adu = input;
-
-        /* Extract MODBUS Header */
-        if (ModbusParseHeader(modbus, &header, adu, adu_len))
-            SCReturnStruct(APP_LAYER_OK);
-
-        /* Update ADU length with length in Modbus header. */
-        adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1;
-        if (adu_len > input_len)
-            SCReturnStruct(APP_LAYER_OK);
-
-        /* Allocate a Transaction Context and add it to Transaction list */
-        tx = ModbusTxAlloc(modbus);
-        if (tx == NULL)
-            SCReturnStruct(APP_LAYER_OK);
-
-        /* Check MODBUS Header */
-        ModbusCheckHeader(modbus, &header);
-
-        /* Store Unit ID, Transaction ID & PDU length */
-        tx->unit_id         = header.unitId;
-        tx->transactionId   = header.transactionId;
-        tx->length          = header.length;
-
-        /* Extract MODBUS PDU and fill Transaction Context */
-        ModbusParseRequestPDU(tx, modbus, adu, adu_len);
-
-        /* Update input line and remaining input length of the command */
-        input       += adu_len;
-        input_len   -= adu_len;
-    }
-
-    SCReturnStruct(APP_LAYER_OK);
-}
-
-/** \internal
- * \brief This function is called to retrieve a Modbus response
- *
- * \param state     Pointer to Modbus state structure for the parser
- * \param input     Input line of the command
- * \param input_len Length of the request
- *
- * \retval AppLayerResult APP_LAYER_OK or APP_LAYER_ERROR
- */
-static AppLayerResult ModbusParseResponse(Flow      *f,
-                               void                 *state,
-                               AppLayerParserState  *pstate,
-                               const uint8_t        *input,
-                               uint32_t             input_len,
-                               void                 *local_data,
-                               const uint8_t        flags)
-{
-    SCEnter();
-    ModbusHeader        header;
-    ModbusState         *modbus = (ModbusState *) state;
-    ModbusTransaction   *tx;
-
-    if (input == NULL && AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC)) {
-        SCReturnStruct(APP_LAYER_OK);
-    } else if (input == NULL || input_len == 0) {
-        SCReturnStruct(APP_LAYER_ERROR);
-    }
-
-    while (input_len > 0) {
-        uint32_t    adu_len = input_len;
-        const uint8_t     *adu = input;
-
-        /* Extract MODBUS Header */
-        if (ModbusParseHeader(modbus, &header, adu, adu_len))
-            SCReturnStruct(APP_LAYER_OK);
-
-        /* Update ADU length with length in Modbus header. */
-        adu_len = (uint32_t) sizeof(ModbusHeader) + (uint32_t) header.length - 1;
-        if (adu_len > input_len)
-            SCReturnStruct(APP_LAYER_OK);
-
-        /* Find the transaction context thanks to transaction ID (and function code) */
-        tx = ModbusTxFindByTransaction(modbus, header.transactionId);
-        if (tx == NULL) {
-            /* Allocate a Transaction Context if not previous request */
-            /* and add it to Transaction list */
-            tx = ModbusTxAlloc(modbus);
-            if (tx == NULL)
-                SCReturnStruct(APP_LAYER_OK);
-
-            SCLogDebug("MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE");
-            ModbusSetEvent(modbus, MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE);
-        } else {
-            /* Store PDU length */
-            tx->length = header.length;
-
-            /* Extract MODBUS PDU and fill Transaction Context */
-            ModbusParseResponsePDU(tx, modbus, adu, adu_len);
-        }
-
-        /* Check and store MODBUS Header */
-        ModbusCheckHeader(modbus, &header);
-
-        /* Mark as completed */
-        tx->replied = 1;
-
-        /* Update input line and remaining input length of the command */
-        input       += adu_len;
-        input_len   -= adu_len;
-    }
-
-    SCReturnStruct(APP_LAYER_OK);
-}
-
-/** \internal
- *     \brief Function to allocate the Modbus state memory
- */
-static void *ModbusStateAlloc(void *orig_state, AppProto proto_orig)
-{
-    ModbusState *modbus;
-
-    modbus = (ModbusState *) SCCalloc(1, sizeof(ModbusState));
-    if (unlikely(modbus == NULL))
-        return NULL;
-
-    TAILQ_INIT(&modbus->tx_list);
-
-    return (void *) modbus;
-}
-
-/** \internal
- *  \brief Function to free the Modbus state memory
- */
-static void ModbusStateFree(void *state)
-{
-    SCEnter();
-    ModbusState         *modbus = (ModbusState *) state;
-    ModbusTransaction   *tx = NULL, *ttx;
-
-    if (state) {
-        TAILQ_FOREACH_SAFE(tx, &modbus->tx_list, next, ttx) {
-            ModbusTxFree(tx);
-        }
-
-        SCFree(state);
-    }
-    SCReturn;
-}
-
-static uint16_t ModbusProbingParser(Flow *f,
-                                    uint8_t direction,
-                                    const uint8_t *input,
-                                    uint32_t    input_len,
-                                    uint8_t *rdir)
-{
-    ModbusHeader *header = (ModbusHeader *) input;
-
-    /* Modbus header is 7 bytes long */
-    if (input_len < sizeof(ModbusHeader))
-        return ALPROTO_UNKNOWN;
-
-    /* MODBUS protocol is identified by the value 0. */
-    if (header->protocolId != 0)
-        return ALPROTO_FAILED;
-
-    return ALPROTO_MODBUS;
-}
-
-static DetectEngineState *ModbusGetTxDetectState(void *vtx)
-{
-    ModbusTransaction *tx = (ModbusTransaction *)vtx;
-    return tx->de_state;
-}
-
-static int ModbusSetTxDetectState(void *vtx, DetectEngineState *s)
-{
-    ModbusTransaction *tx = (ModbusTransaction *)vtx;
-    tx->de_state = s;
-    return 0;
-}
-
-/**
- * \brief Function to register the Modbus protocol parsers and other functions
+ * \brief Function to register the Modbus protocol parser
  */
 void RegisterModbusParsers(void)
 {
-    SCEnter();
-    const char *proto_name = "modbus";
-
-    /* Modbus application protocol V1.1b3 */
-    if (AppLayerProtoDetectConfProtoDetectionEnabled("tcp", proto_name)) {
-        AppLayerProtoDetectRegisterProtocol(ALPROTO_MODBUS, proto_name);
-
-        if (RunmodeIsUnittests()) {
-            AppLayerProtoDetectPPRegister(IPPROTO_TCP,
-                                          "502",
-                                          ALPROTO_MODBUS,
-                                          0, sizeof(ModbusHeader),
-                                          STREAM_TOSERVER,
-                                          ModbusProbingParser, ModbusProbingParser);
-        } else {
-            /* If there is no app-layer section for Modbus, silently
-             * leave it disabled. */
-            if (!AppLayerProtoDetectPPParseConfPorts("tcp", IPPROTO_TCP,
-                                                proto_name, ALPROTO_MODBUS,
-                                                0, sizeof(ModbusHeader),
-                                                ModbusProbingParser, ModbusProbingParser)) {
-                return;
-            }
-        }
-
-        ConfNode *p = NULL;
-        p = ConfGetNode("app-layer.protocols.modbus.request-flood");
-        if (p != NULL) {
-            uint32_t value;
-            if (ParseSizeStringU32(p->val, &value) < 0) {
-                SCLogError(SC_ERR_MODBUS_CONFIG, "invalid value for request-flood %s", p->val);
-            } else {
-                request_flood = value;
-            }
-        }
-        SCLogConfig("Modbus request flood protection level: %u", request_flood);
-
-        p = ConfGetNode("app-layer.protocols.modbus.stream-depth");
-        if (p != NULL) {
-            uint32_t value;
-            if (ParseSizeStringU32(p->val, &value) < 0) {
-                SCLogError(SC_ERR_MODBUS_CONFIG, "invalid value for stream-depth %s", p->val);
-            } else {
-                stream_depth = value;
-            }
-        }
-        SCLogConfig("Modbus stream depth: %u", stream_depth);
-    } else {
-        SCLogConfig("Protocol detection and parser disabled for %s protocol.", proto_name);
-        return;
-    }
-    if (AppLayerParserConfParserEnabled("tcp", proto_name)) {
-        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER, ModbusParseRequest);
-        AppLayerParserRegisterParser(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOCLIENT, ModbusParseResponse);
-        AppLayerParserRegisterStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateAlloc, ModbusStateFree);
-
-        AppLayerParserRegisterGetEventsFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetEvents);
-        AppLayerParserRegisterDetectStateFuncs(IPPROTO_TCP, ALPROTO_MODBUS,
-                                               ModbusGetTxDetectState, ModbusSetTxDetectState);
-
-        AppLayerParserRegisterGetTx(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTx);
-        AppLayerParserRegisterTxDataFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTxData);
-        AppLayerParserRegisterGetTxCnt(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetTxCnt);
-        AppLayerParserRegisterTxFreeFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateTxFree);
-
-        AppLayerParserRegisterGetStateProgressFunc(IPPROTO_TCP, ALPROTO_MODBUS, ModbusGetAlstateProgress);
-        AppLayerParserRegisterStateProgressCompletionStatus(ALPROTO_MODBUS, 1, 1);
-
-        AppLayerParserRegisterGetEventInfo(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateGetEventInfo);
-        AppLayerParserRegisterGetEventInfoById(IPPROTO_TCP, ALPROTO_MODBUS, ModbusStateGetEventInfoById);
-
-        AppLayerParserRegisterParserAcceptableDataDirection(IPPROTO_TCP, ALPROTO_MODBUS, STREAM_TOSERVER);
-
-        AppLayerParserSetStreamDepth(IPPROTO_TCP, ALPROTO_MODBUS, stream_depth);
-    } else {
-        SCLogConfig("Parsed disabled for %s protocol. Protocol detection" "still on.", proto_name);
-    }
+    rs_modbus_register_parser();
 #ifdef UNITTESTS
     AppLayerParserRegisterProtocolUnittests(IPPROTO_TCP, ALPROTO_MODBUS, ModbusParserRegisterTests);
 #endif
@@ -1556,6 +70,11 @@ void RegisterModbusParsers(void)
 #include "stream-tcp.h"
 #include "stream-tcp-private.h"
 
+#include "rust.h"
+
+/* Modbus default stream reassembly depth */
+#define MODBUS_CONFIG_DEFAULT_STREAM_DEPTH 0
+
 /* Modbus Application Protocol Specification V1.1b3 6.1: Read Coils */
 static uint8_t invalidFunctionCode[] = {
     /* Transaction ID */ 0x00, 0x00,
@@ -1770,13 +289,14 @@ static int ModbusParserTest01(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
-    FAIL_IF_NOT(tx->function == 1);
-    FAIL_IF_NOT(tx->read.address == 0x7890);
-    FAIL_IF_NOT(tx->read.quantity == 19);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1);
+    FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890);
+    FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19);
 
     FLOWLOCK_WRLOCK(&f);
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
@@ -1785,7 +305,7 @@ static int ModbusParserTest01(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    FAIL_IF_NOT(modbus_state->transaction_max == 1);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
 
     AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(true);
@@ -1818,17 +338,22 @@ static int ModbusParserTest02(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 16);
+    FAIL_IF_NOT(rs_modbus_message_get_write_multreq_address(&request) == 0x01);
+    FAIL_IF_NOT(rs_modbus_message_get_write_multreq_quantity(&request) == 2);
 
-    FAIL_IF_NOT(tx->function == 16);
-    FAIL_IF_NOT(tx->write.address == 0x01);
-    FAIL_IF_NOT(tx->write.quantity == 2);
-    FAIL_IF_NOT(tx->write.count == 4);
-    FAIL_IF_NOT(tx->data[0] == 0x000A);
-    FAIL_IF_NOT(tx->data[1] == 0x0102);
+    size_t data_len;
+    const uint8_t *data = rs_modbus_message_get_write_multreq_data(&request, &data_len);
+    FAIL_IF_NOT(data_len == 4);
+    FAIL_IF_NOT(data[0] == 0x00);
+    FAIL_IF_NOT(data[1] == 0x0A);
+    FAIL_IF_NOT(data[2] == 0x01);
+    FAIL_IF_NOT(data[3] == 0x02);
 
     FLOWLOCK_WRLOCK(&f);
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
@@ -1837,7 +362,7 @@ static int ModbusParserTest02(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    FAIL_IF_NOT(modbus_state->transaction_max == 1);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
 
     AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(true);
@@ -1898,20 +423,27 @@ static int ModbusParserTest03(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
-
-    FAIL_IF_NOT(tx->function == 23);
-    FAIL_IF_NOT(tx->read.address == 0x03);
-    FAIL_IF_NOT(tx->read.quantity == 6);
-    FAIL_IF_NOT(tx->write.address == 0x0E);
-    FAIL_IF_NOT(tx->write.quantity == 3);
-    FAIL_IF_NOT(tx->write.count == 6);
-    FAIL_IF_NOT(tx->data[0] == 0x1234);
-    FAIL_IF_NOT(tx->data[1] == 0x5678);
-    FAIL_IF_NOT(tx->data[2] == 0x9ABC);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
+
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 23);
+    FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_read_address(&request) == 0x03);
+    FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_read_quantity(&request) == 6);
+    FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_write_address(&request) == 0x0E);
+    FAIL_IF_NOT(rs_modbus_message_get_rw_multreq_write_quantity(&request) == 3);
+
+    size_t data_len;
+    uint8_t const *data = rs_modbus_message_get_rw_multreq_write_data(&request, &data_len);
+    FAIL_IF_NOT(data_len == 6);
+    FAIL_IF_NOT(data[0] == 0x12);
+    FAIL_IF_NOT(data[1] == 0x34);
+    FAIL_IF_NOT(data[2] == 0x56);
+    FAIL_IF_NOT(data[3] == 0x78);
+    FAIL_IF_NOT(data[4] == 0x9A);
+    FAIL_IF_NOT(data[5] == 0xBC);
 
     FLOWLOCK_WRLOCK(&f);
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
@@ -1920,7 +452,7 @@ static int ModbusParserTest03(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    FAIL_IF_NOT(modbus_state->transaction_max == 1);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -1965,13 +497,14 @@ static int ModbusParserTest04(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
 
-    FAIL_IF_NOT(tx->function == 8);
-    FAIL_IF_NOT(tx->subFunction == 4);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 8);
+    FAIL_IF_NOT(rs_modbus_message_get_subfunction(&request) == 4);
 
     AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(true);
@@ -2031,7 +564,7 @@ static int ModbusParserTest05(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
     /* do detect */
@@ -2104,7 +637,7 @@ static int ModbusParserTest06(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
     /* do detect */
@@ -2175,10 +708,10 @@ static int ModbusParserTest07(void) {
                                 STREAM_TOSERVER,
                                 invalidLengthWriteMultipleRegistersReq,
                                 sizeof(invalidLengthWriteMultipleRegistersReq));
-    FAIL_IF_NOT(r == 0);
+    FAIL_IF_NOT(r == 1);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
     /* do detect */
@@ -2251,14 +784,15 @@ static int ModbusParserTest08(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
 
-    FAIL_IF_NOT(tx->function == 1);
-    FAIL_IF_NOT(tx->read.address == 0x7890);
-    FAIL_IF_NOT(tx->read.quantity == 19);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1);
+    FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890);
+    FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19);
 
     FLOWLOCK_WRLOCK(&f);
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
@@ -2267,7 +801,7 @@ static int ModbusParserTest08(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    FAIL_IF_NOT(modbus_state->transaction_max == 1);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -2311,21 +845,22 @@ static int ModbusParserTest09(void) {
     FLOWLOCK_WRLOCK(&f);
     int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
                                 STREAM_TOSERVER, input, input_len - part2_len);
-    FAIL_IF_NOT(r == 0);
+    FAIL_IF_NOT(r == 1);
 
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
                             STREAM_TOSERVER, input, input_len);
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
 
-    FAIL_IF_NOT(tx->function == 1);
-    FAIL_IF_NOT(tx->read.address == 0x7890);
-    FAIL_IF_NOT(tx->read.quantity == 19);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 1);
+    FAIL_IF_NOT(rs_modbus_message_get_read_request_address(&request) == 0x7890);
+    FAIL_IF_NOT(rs_modbus_message_get_read_request_quantity(&request) == 19);
 
     input_len = sizeof(readCoilsRsp);
     part2_len = 10;
@@ -2334,14 +869,14 @@ static int ModbusParserTest09(void) {
     FLOWLOCK_WRLOCK(&f);
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
                             STREAM_TOCLIENT, input, input_len - part2_len);
-    FAIL_IF_NOT(r == 0);
+    FAIL_IF_NOT(r == 1);
 
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
                             STREAM_TOCLIENT, input, input_len);
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    FAIL_IF_NOT(modbus_state->transaction_max ==1);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
 
     AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(true);
@@ -2382,19 +917,25 @@ static int ModbusParserTest10(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    FAIL_IF_NOT(modbus_state->transaction_max == 2);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 2);
+
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 1);
+    FAIL_IF_NULL(request._0);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 1);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 16);
+    FAIL_IF_NOT(rs_modbus_message_get_write_multreq_address(&request) == 0x01);
+    FAIL_IF_NOT(rs_modbus_message_get_write_multreq_quantity(&request) == 2);
 
-    FAIL_IF_NOT(tx->function == 16);
-    FAIL_IF_NOT(tx->write.address == 0x01);
-    FAIL_IF_NOT(tx->write.quantity == 2);
-    FAIL_IF_NOT(tx->write.count == 4);
-    FAIL_IF_NOT(tx->data[0] == 0x000A);
-    FAIL_IF_NOT(tx->data[1] == 0x0102);
+    size_t data_len;
+    uint8_t const *data = rs_modbus_message_get_write_multreq_data(&request, &data_len);
+    FAIL_IF_NOT(data_len == 4);
+    FAIL_IF_NOT(data[0] == 0x00);
+    FAIL_IF_NOT(data[1] == 0x0A);
+    FAIL_IF_NOT(data[2] == 0x01);
+    FAIL_IF_NOT(data[3] == 0x02);
 
     input_len = sizeof(readCoilsRsp) + sizeof(writeMultipleRegistersRsp);
 
@@ -2476,7 +1017,7 @@ static int ModbusParserTest11(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
     /* do detect */
@@ -2550,7 +1091,7 @@ static int ModbusParserTest12(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
     /* do detect */
@@ -2596,14 +1137,15 @@ static int ModbusParserTest13(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
 
-    FAIL_IF_NOT(tx->function == 22);
-    FAIL_IF_NOT(tx->data[0] == 0x00F2);
-    FAIL_IF_NOT(tx->data[1] == 0x0025);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 22);
+    FAIL_IF_NOT(rs_modbus_message_get_and_mask(&request) == 0x00F2);
+    FAIL_IF_NOT(rs_modbus_message_get_or_mask(&request) == 0x0025);
 
     FLOWLOCK_WRLOCK(&f);
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
@@ -2612,7 +1154,7 @@ static int ModbusParserTest13(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    FAIL_IF_NOT(modbus_state->transaction_max == 1);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
 
     AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(true);
@@ -2645,14 +1187,15 @@ static int ModbusParserTest14(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
 
-    FAIL_IF_NOT(tx->function == 6);
-    FAIL_IF_NOT(tx->write.address == 0x0001);
-    FAIL_IF_NOT(tx->data[0] == 0x0003);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 6);
+    FAIL_IF_NOT(rs_modbus_message_get_write_address(&request) == 0x0001);
+    FAIL_IF_NOT(rs_modbus_message_get_write_data(&request) == 0x0003);
 
     FLOWLOCK_WRLOCK(&f);
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS,
@@ -2661,7 +1204,7 @@ static int ModbusParserTest14(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    FAIL_IF_NOT(modbus_state->transaction_max == 1);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
 
     AppLayerParserThreadCtxFree(alp_tctx);
     StreamTcpFreeConfig(true);
@@ -2721,12 +1264,13 @@ static int ModbusParserTest15(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
 
-    FAIL_IF_NOT(tx->function == 22);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 22);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -2740,7 +1284,11 @@ static int ModbusParserTest15(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    FAIL_IF_NOT(modbus_state->transaction_max == 1);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+    ModbusMessage response = rs_modbus_state_get_tx_response(modbus_state, 0);
+    FAIL_IF_NULL(response._0);
+
+    FAIL_IF_NOT(rs_modbus_message_get_function(&response) == 22);
 
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
@@ -2808,13 +1356,18 @@ static int ModbusParserTest16(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
-    ModbusTransaction *tx = ModbusGetTx(modbus_state, 0);
+    ModbusMessage request = rs_modbus_state_get_tx_request(modbus_state, 0);
+    FAIL_IF_NULL(request._0);
 
-    FAIL_IF_NOT(tx->function == 6);
-    FAIL_IF_NOT(tx->write.address == 0x0001);
+    FAIL_IF_NOT(rs_modbus_message_get_function(&request) == 6);
+    size_t data_len;
+    const uint8_t *data = rs_modbus_message_get_bytevec_data(&request, &data_len);
+    FAIL_IF_NOT(data_len == 2);
+    FAIL_IF_NOT(data[0] == 0x00);
+    FAIL_IF_NOT(data[1] == 0x01);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -2828,7 +1381,12 @@ static int ModbusParserTest16(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    FAIL_IF_NOT(modbus_state->transaction_max == 1);
+    FAIL_IF_NOT(rs_modbus_state_get_tx_count(modbus_state) == 1);
+    ModbusMessage response = rs_modbus_state_get_tx_response(modbus_state, 0);
+    FAIL_IF_NULL(response._0);
+
+    FAIL_IF_NOT(rs_modbus_message_get_function(&response) == 6);
+    FAIL_IF_NOT(rs_modbus_message_get_write_address(&response) == 0x0001);
 
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
@@ -2908,7 +1466,7 @@ static int ModbusParserTest18(void) {
     FLOWLOCK_WRLOCK(&f);
     int r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOSERVER,
                                 input, input_len - part2_len);
-    FAIL_IF(r != 0);
+    FAIL_IF(r != 1);
     FLOWLOCK_UNLOCK(&f);
 
     FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
@@ -2930,7 +1488,7 @@ static int ModbusParserTest18(void) {
     FLOWLOCK_WRLOCK(&f);
     r = AppLayerParserParse(NULL, alp_tctx, &f, ALPROTO_MODBUS, STREAM_TOCLIENT,
                             input, input_len - part2_len);
-    FAIL_IF(r != 0);
+    FAIL_IF(r != 1);
     FLOWLOCK_UNLOCK(&f);
 
     FAIL_IF(((TcpSession *)(f.protoctx))->reassembly_depth != MODBUS_CONFIG_DEFAULT_STREAM_DEPTH);
@@ -3002,7 +1560,7 @@ static int ModbusParserTest19(void) {
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
+    ModbusState *modbus_state = f.alstate;
     FAIL_IF_NULL(modbus_state);
 
     /* do detect */
index 467837bd66bc1495a7241d15e343473d71210094..5d97fdcedef5d651ab2d1c68312152a7b1e2decf 100644 (file)
 #ifndef __APP_LAYER_MODBUS_H__
 #define __APP_LAYER_MODBUS_H__
 
-#include "decode.h"
-#include "detect-engine-state.h"
-#include "queue.h"
-#include "rust.h"
-
-/* Modbus Application Data Unit (ADU)
- * and Protocol Data Unit (PDU) messages */
-enum {
-    MODBUS_DECODER_EVENT_INVALID_PROTOCOL_ID,
-    MODBUS_DECODER_EVENT_UNSOLICITED_RESPONSE,
-    MODBUS_DECODER_EVENT_INVALID_LENGTH,
-    MODBUS_DECODER_EVENT_INVALID_UNIT_IDENTIFIER,
-    MODBUS_DECODER_EVENT_INVALID_FUNCTION_CODE,
-    MODBUS_DECODER_EVENT_INVALID_VALUE,
-    MODBUS_DECODER_EVENT_INVALID_EXCEPTION_CODE,
-    MODBUS_DECODER_EVENT_VALUE_MISMATCH,
-    MODBUS_DECODER_EVENT_FLOODED,
-};
-
 /* Modbus Function Code Categories. */
 #define MODBUS_CAT_NONE                 0x0
 #define MODBUS_CAT_PUBLIC_ASSIGNED      (1<<0)
@@ -79,57 +60,6 @@ enum {
 #define MODBUS_TYP_WRITE_MULTIPLE       (MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
 #define MODBUS_TYP_READ_WRITE_MULTIPLE  (MODBUS_TYP_READ | MODBUS_TYP_WRITE | MODBUS_TYP_MULTIPLE)
 
-/* Modbus Function Code. */
-#define MODBUS_FUNC_NONE                0x00
-
-/* Modbus Transaction Structure, request/response. */
-typedef struct ModbusTransaction_ {
-    struct ModbusState_ *modbus;
-
-    uint64_t    tx_num;         /**< internal: id */
-    uint16_t    transactionId;
-    uint16_t    length;
-    uint8_t     unit_id;
-    uint8_t     function;
-    uint8_t     category;
-    uint8_t     type;
-    uint8_t     replied;                    /**< bool indicating request is replied to. */
-
-    union {
-        uint16_t    subFunction;
-        uint8_t     mei;
-        struct {
-            struct {
-                uint16_t    address;
-                uint16_t    quantity;
-            } read;
-            struct {
-                uint16_t    address;
-                uint16_t    quantity;
-                uint8_t     count;
-            } write;
-        };
-    };
-    uint16_t    *data;  /**< to store data to write, bit is converted in 16bits. */
-
-    AppLayerDecoderEvents *decoder_events;  /**< per tx events */
-    DetectEngineState *de_state;
-    AppLayerTxData tx_data;
-
-    TAILQ_ENTRY(ModbusTransaction_) next;
-} ModbusTransaction;
-
-/* Modbus State Structure. */
-typedef struct ModbusState_ {
-    TAILQ_HEAD(, ModbusTransaction_)    tx_list;    /**< transaction list */
-    ModbusTransaction                   *curr;      /**< ptr to current tx */
-    uint64_t                            transaction_max;
-    uint32_t                            unreplied_cnt;  /**< number of unreplied requests */
-    uint16_t                            events;
-    uint8_t                             givenup;    /**< bool indicating flood. */
-} ModbusState;
-
 void RegisterModbusParsers(void);
-void ModbusParserRegisterTests(void);
 
 #endif /* __APP_LAYER_MODBUS_H__ */
index 6c1551457a95d7043d222d73b1ee4bab013360f8..354a73c707af028ae1d0fa954d177e39a2d9134b 100644 (file)
@@ -35,7 +35,6 @@
 #include "suricata-common.h"
 
 #include "app-layer.h"
-#include "app-layer-modbus.h"
 
 #include "detect.h"
 #include "detect-modbus.h"
 
 #include "util-debug.h"
 
-/** \internal
- *
- * \brief Value match detection code
- *
- *  \param  value   Modbus value context (min, max and mode)
- *  \param  min     Minimum value to compare
- *  \param  inter   Interval or maximum (min + inter) value to compare
- *
- *  \retval 1 match or 0 no match
- */
-static int DetectEngineInspectModbusValueMatch(DetectModbusValue    *value,
-                                               uint16_t             min,
-                                               uint16_t             inter)
-{
-    SCEnter();
-    uint16_t max = min + inter;
-
-    int ret = 0;
-
-    switch (value->mode) {
-        case DETECT_MODBUS_EQ:
-            if ((value->min >= min) && (value->min <= max))
-                ret = 1;
-            break;
-
-        case DETECT_MODBUS_LT:
-            if (value->min > min)
-                ret = 1;
-            break;
-
-        case DETECT_MODBUS_GT:
-            if (value->min < max)
-                ret = 1;
-            break;
-
-        case DETECT_MODBUS_RA:
-            if ((value->max > min) && (value->min < max))
-                ret = 1;
-            break;
-    }
-
-    SCReturnInt(ret);
-}
-
-/** \internal
- *
- * \brief Do data (and address) inspection & validation for a signature
- *
- *  \param tx       Pointer to Modbus Transaction
- *  \param address  Address inspection
- *  \param data     Pointer to data signature structure to match
- *
- *  \retval 0 no match or 1 match
- */
-static int DetectEngineInspectModbusData(ModbusTransaction  *tx,
-                                         uint16_t           address,
-                                         DetectModbusValue  *data)
-{
-    SCEnter();
-    uint16_t offset, value = 0, type = tx->type;
-
-    if (type & MODBUS_TYP_SINGLE) {
-        /* Output/Register(s) Value */
-        if (type & MODBUS_TYP_COILS)
-            value = (tx->data[0])? 1 : 0;
-        else
-            value = tx->data[0];
-    } else if (type & MODBUS_TYP_MULTIPLE) {
-        int i, size = (int) sizeof(tx->data);
-
-        offset = address - (tx->write.address + 1);
-
-        /* In case of Coils, offset is in bit (convert in byte) */
-        if (type & MODBUS_TYP_COILS)
-            offset >>= 3;
-
-        for (i=0; i< size; i++) {
-            /* Select the correct register/coils amongst the output value */
-            if (!(offset--)) {
-                value = tx->data[i];
-                break;
-            }
-        }
-
-        /* In case of Coils,  offset is now in the bit is the rest of previous convert */
-        if (type & MODBUS_TYP_COILS) {
-            offset  = (address - (tx->write.address + 1)) & 0x7;
-            value   = (value >> offset) & 0x1;
-        }
-    } else {
-        /* It is not possible to define the value that is writing for Mask      */
-        /* Write Register function because the current content is not available.*/
-        SCReturnInt(0);
-    }
-
-    SCReturnInt(DetectEngineInspectModbusValueMatch(data, value, 0));
-}
-
-/** \internal
- *
- * \brief Do address inspection & validation for a signature
- *
- *  \param tx       Pointer to Modbus Transaction
- *  \param address  Pointer to address signature structure to match
- *  \param access   Access mode (READ or WRITE)
- *
- *  \retval 0 no match or 1 match
- */
-static int DetectEngineInspectModbusAddress(ModbusTransaction   *tx,
-                                            DetectModbusValue   *address,
-                                            uint8_t             access)
-{
-    SCEnter();
-    int ret = 0;
-
-    /* Check if read/write address of request is at/in the address range of signature */
-    if (access == MODBUS_TYP_READ) {
-        /* In the PDU Coils are addresses starting at zero */
-        /* therefore Coils numbered 1-16 are addressed as 0-15 */
-        ret = DetectEngineInspectModbusValueMatch(address,
-                                                  tx->read.address + 1,
-                                                  tx->read.quantity - 1);
-    } else {
-        /* In the PDU Registers are addresses starting at zero */
-        /* therefore Registers numbered 1-16 are addressed as 0-15 */
-        if (tx->type & MODBUS_TYP_SINGLE)
-            ret = DetectEngineInspectModbusValueMatch(address,
-                                                      tx->write.address + 1,
-                                                      0);
-        else
-            ret = DetectEngineInspectModbusValueMatch(address,
-                                                      tx->write.address + 1,
-                                                      tx->write.quantity - 1);
-    }
-
-    SCReturnInt(ret);
-}
-
-/** \brief Do the content inspection & validation for a signature
- *
- *  \param de_ctx   Detection engine context
- *  \param det_ctx  Detection engine thread context
- *  \param s        Signature to inspect ( and sm: SigMatch to inspect)
- *  \param f        Flow
- *  \param flags    App layer flags
- *  \param alstate  App layer state
- *  \param txv      Pointer to Modbus Transaction structure
- *
- *  \retval 0 no match or 1 match
- */
-int DetectEngineInspectModbus(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
-        const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
-        uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
-{
-    SCEnter();
-    ModbusTransaction   *tx = (ModbusTransaction *)txv;
-    DetectModbus *modbus = (DetectModbus *)engine->smd->ctx;
-
-    int ret = 0;
-
-    if (modbus == NULL) {
-        SCLogDebug("no modbus state, no match");
-        SCReturnInt(0);
-    }
-
-    if (modbus->unit_id != NULL) {
-        if (DetectEngineInspectModbusValueMatch(modbus->unit_id, tx->unit_id, 0) == 0) {
-            SCReturnInt(0);
-        } else {
-            ret = 1;
-        }
-    }
-
-    if (modbus->type == MODBUS_TYP_NONE) {
-        if (modbus->category == MODBUS_CAT_NONE) {
-            if (modbus->function != MODBUS_FUNC_NONE) {
-                if (modbus->function == tx->function) {
-                    if (modbus->has_subfunction) {
-                        SCLogDebug("looking for Modbus server function %d and subfunction %d",
-                                   modbus->function, modbus->subfunction);
-                        ret = (modbus->subfunction == (tx->subFunction))? 1 : 0;
-                    } else {
-                        SCLogDebug("looking for Modbus server function %d", modbus->function);
-                        ret = 1;
-                    }
-                } else {
-                    ret = 0;
-                }
-            }
-        } else {
-            SCLogDebug("looking for Modbus category function %d", modbus->category);
-            ret = (tx->category & modbus->category)? 1 : 0;
-        }
-    } else {
-        uint8_t access      = modbus->type & MODBUS_TYP_ACCESS_MASK;
-        uint8_t function    = modbus->type & MODBUS_TYP_ACCESS_FUNCTION_MASK;
-
-        if (access != MODBUS_TYP_NONE) {
-            if ((access & tx->type) && ((function == MODBUS_TYP_NONE) || (function & tx->type))) {
-                if (modbus->address != NULL) {
-                    ret = DetectEngineInspectModbusAddress(tx, modbus->address, access);
-
-                    if (ret && (modbus->data != NULL)) {
-                        ret = DetectEngineInspectModbusData(tx, modbus->address->min, modbus->data);
-                    }
-                } else {
-                    SCLogDebug("looking for Modbus access type %d and function type %d", access, function);
-                    ret = 1;
-                }
-            } else {
-                ret = 0;
-            }
-        }
-    }
-
-    SCReturnInt(ret);
-}
-
 #ifdef UNITTESTS /* UNITTESTS */
 #include "app-layer-parser.h"
 
@@ -396,8 +177,7 @@ static int DetectEngineInspectModbusTest01(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -467,8 +247,7 @@ static int DetectEngineInspectModbusTest02(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -539,8 +318,7 @@ static int DetectEngineInspectModbusTest03(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -610,8 +388,7 @@ static int DetectEngineInspectModbusTest04(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -681,8 +458,7 @@ static int DetectEngineInspectModbusTest05(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -752,8 +528,7 @@ static int DetectEngineInspectModbusTest06(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -823,8 +598,7 @@ static int DetectEngineInspectModbusTest07(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -942,8 +716,7 @@ static int DetectEngineInspectModbusTest08(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -1074,8 +847,7 @@ static int DetectEngineInspectModbusTest09(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -1196,8 +968,7 @@ static int DetectEngineInspectModbusTest10(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -1313,8 +1084,7 @@ static int DetectEngineInspectModbusTest11(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
@@ -1411,8 +1181,7 @@ static int DetectEngineInspectModbusTest12(void)
     FAIL_IF_NOT(r == 0);
     FLOWLOCK_UNLOCK(&f);
 
-    ModbusState    *modbus_state = f.alstate;
-    FAIL_IF_NULL(modbus_state);
+    FAIL_IF_NULL(f.alstate);
 
     /* do detect */
     SigMatchSignatures(&tv, de_ctx, det_ctx, p);
index c4e3a5f08382b809a5e6b4e5c415c3415aa11d8e..0fe6d345f845009f57a158ebd84f815b0c039f54 100644 (file)
@@ -48,7 +48,6 @@
 #include "detect-engine.h"
 
 #include "detect-modbus.h"
-#include "detect-engine-modbus.h"
 
 #include "util-debug.h"
 #include "util-byte.h"
 #include "app-layer-modbus.h"
 
 #include "stream-tcp.h"
-
-/**
- * \brief Regex for parsing the Modbus unit id string
- */
-#define PARSE_REGEX_UNIT_ID "^\\s*\"?\\s*unit\\s+([<>]?\\d+)(<>\\d+)?(,\\s*(.*))?\\s*\"?\\s*$"
-static DetectParseRegex unit_id_parse_regex;
-
-/**
- * \brief Regex for parsing the Modbus function string
- */
-#define PARSE_REGEX_FUNCTION "^\\s*\"?\\s*function\\s*(!?[A-z0-9]+)(,\\s*subfunction\\s+(\\d+))?\\s*\"?\\s*$"
-static DetectParseRegex function_parse_regex;
-
-/**
- * \brief Regex for parsing the Modbus access string
- */
-#define PARSE_REGEX_ACCESS "^\\s*\"?\\s*access\\s*(read|write)\\s*(discretes|coils|input|holding)?(,\\s*address\\s+([<>]?\\d+)(<>\\d+)?(,\\s*value\\s+([<>]?\\d+)(<>\\d+)?)?)?\\s*\"?\\s*$"
-static DetectParseRegex access_parse_regex;
+#include "rust.h"
 
 static int g_modbus_buffer_id = 0;
 
 #ifdef UNITTESTS
-void DetectModbusRegisterTests(void);
+static void DetectModbusRegisterTests(void);
 #endif
 
 /** \internal
@@ -89,413 +71,12 @@ void DetectModbusRegisterTests(void);
  */
 static void DetectModbusFree(DetectEngineCtx *de_ctx, void *ptr) {
     SCEnter();
-    DetectModbus *modbus = (DetectModbus *) ptr;
-
-    if(modbus) {
-        if (modbus->unit_id)
-            SCFree(modbus->unit_id);
-
-        if (modbus->address)
-            SCFree(modbus->address);
-
-        if (modbus->data)
-            SCFree(modbus->data);
-
-        SCFree(modbus);
-    }
-}
-
-/** \internal
- *
- * \brief This function is used to parse Modbus parameters in access mode
- *
- * \param de_ctx Pointer to the detection engine context
- * \param str Pointer to the user provided id option
- *
- * \retval Pointer to DetectModbusData on success or NULL on failure
- */
-static DetectModbus *DetectModbusAccessParse(DetectEngineCtx *de_ctx, const char *str)
-{
-    SCEnter();
-    DetectModbus *modbus = NULL;
-
-    char    arg[MAX_SUBSTRINGS];
-    int     ov[MAX_SUBSTRINGS], ret, res;
-
-    ret = DetectParsePcreExec(&access_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS);
-    if (ret < 1)
-        goto error;
-
-    res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
-    if (res < 0) {
-        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-        goto error;
-    }
-
-    /* We have a correct Modbus option */
-    modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
-    if (unlikely(modbus == NULL))
-        goto error;
-
-    if (strcmp(arg, "read") == 0)
-        modbus->type = MODBUS_TYP_READ;
-    else if (strcmp(arg, "write") == 0)
-        modbus->type = MODBUS_TYP_WRITE;
-    else
-        goto error;
-
-    if (ret > 2) {
-        res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS);
-        if (res < 0) {
-            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-            goto error;
-        }
-
-        if (*arg != '\0') {
-            if (strcmp(arg, "discretes") == 0) {
-                if (modbus->type == MODBUS_TYP_WRITE)
-                    /* Discrete access is only read access. */
-                    goto error;
-
-                modbus->type |= MODBUS_TYP_DISCRETES;
-            }
-            else if (strcmp(arg, "coils") == 0) {
-                modbus->type |= MODBUS_TYP_COILS;
-            }
-            else if (strcmp(arg, "input") == 0) {
-                if (modbus->type == MODBUS_TYP_WRITE) {
-                    /* Input access is only read access. */
-                    goto error;
-                }
-
-                modbus->type |= MODBUS_TYP_INPUT;
-            }
-            else if (strcmp(arg, "holding") == 0) {
-                modbus->type |= MODBUS_TYP_HOLDING;
-            }
-            else
-                goto error;
-        }
-
-        if (ret > 4) {
-            res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 4, arg, MAX_SUBSTRINGS);
-            if (res < 0) {
-                SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-                goto error;
-            }
-
-            /* We have a correct address option */
-            modbus->address = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
-            if (unlikely(modbus->address == NULL))
-                goto error;
-
-            uint8_t idx;
-            if (arg[0] == '>') {
-                idx = 1;
-                modbus->address->mode   = DETECT_MODBUS_GT;
-            } else if (arg[0] == '<') {
-                idx = 1;
-                modbus->address->mode   = DETECT_MODBUS_LT;
-            } else {
-                idx = 0;
-            }
-            if (StringParseUint16(&modbus->address->min, 10, 0,
-                                  (const char *) (arg + idx)) < 0) {
-                SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for min "
-                           "address: %s", (const char *)(arg + idx));
-                goto error;
-            }
-            SCLogDebug("and min/equal address %d", modbus->address->min);
-
-            if (ret > 5) {
-                res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 5, arg, MAX_SUBSTRINGS);
-                if (res < 0) {
-                    SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-                    goto error;
-                }
-
-                if (*arg != '\0') {
-                    if (StringParseUint16(&modbus->address->max, 10, 0,
-                                         (const char*) (arg + 2)) < 0) {
-                        SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for max "
-                                   "address: %s", (const char*)(arg + 2));
-                        goto error;
-                    }
-                    modbus->address->mode   = DETECT_MODBUS_RA;
-                    SCLogDebug("and max address %d", modbus->address->max);
-                }
-
-                if (ret > 7) {
-                    res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 7, arg, MAX_SUBSTRINGS);
-                    if (res < 0) {
-                        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-                        goto error;
-                    }
-
-                    if (modbus->address->mode != DETECT_MODBUS_EQ) {
-                        SCLogError(SC_ERR_CONFLICTING_RULE_KEYWORDS, "rule contains conflicting keywords (address range and value).");
-                        goto error;
-                    }
-
-                    /* We have a correct address option */
-                    if (modbus->type == MODBUS_TYP_READ)
-                        /* Value access is only possible in write access. */
-                        goto error;
-
-                    modbus->data = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
-                    if (unlikely(modbus->data == NULL))
-                        goto error;
-
-
-                    uint8_t idx_mode;
-                    if (arg[0] == '>') {
-                        idx_mode = 1;
-                        modbus->data->mode  = DETECT_MODBUS_GT;
-                    } else if (arg[0] == '<') {
-                        idx_mode = 1;
-                        modbus->data->mode  = DETECT_MODBUS_LT;
-                    } else {
-                        idx_mode = 0;
-                    }
-                    if (StringParseUint16(&modbus->data->min, 10, 0,
-                                          (const char*) (arg + idx_mode)) < 0) {
-                        SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
-                                   "min data: %s", (const char*)(arg + idx_mode));
-                        goto error;
-                    }
-                    SCLogDebug("and min/equal value %d", modbus->data->min);
-
-                    if (ret > 8) {
-                        res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 8, arg, MAX_SUBSTRINGS);
-                        if (res < 0) {
-                            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-                            goto error;
-                        }
-
-                        if (*arg != '\0') {
-                            if (StringParseUint16(&modbus->data->max,
-                                                  10, 0, (const char*) (arg + 2)) < 0) {
-                                SCLogError(SC_ERR_INVALID_VALUE,
-                                           "Invalid value for max data: %s",
-                                           (const char*)(arg + 2));
-                                goto error;
-                            }
-                            modbus->data->mode  = DETECT_MODBUS_RA;
-                            SCLogDebug("and max value %d", modbus->data->max);
-                        }
-                    }
-                }
-            }
-        }
-    }
-
-    SCReturnPtr(modbus, "DetectModbusAccess");
-
-error:
-    if (modbus != NULL)
-        DetectModbusFree(de_ctx, modbus);
-
-    SCReturnPtr(NULL, "DetectModbus");
-}
-
-/** \internal
- *
- * \brief This function is used to parse Modbus parameters in function mode
- *
- * \param str Pointer to the user provided id option
- *
- * \retval id_d pointer to DetectModbusData on success
- * \retval NULL on failure
- */
-static DetectModbus *DetectModbusFunctionParse(DetectEngineCtx *de_ctx, const char *str)
-{
-    SCEnter();
-    DetectModbus *modbus = NULL;
-
-    char    arg[MAX_SUBSTRINGS], *ptr = arg;
-    int     ov[MAX_SUBSTRINGS], res, ret;
-
-    ret = DetectParsePcreExec(&function_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS);
-    if (ret < 1)
-        goto error;
-
-    res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
-    if (res < 0) {
-        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-        goto error;
-    }
-
-    /* We have a correct Modbus function option */
-    modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
-    if (unlikely(modbus == NULL))
-        goto error;
-
-    if (isdigit((unsigned char)ptr[0])) {
-        if (StringParseUint8(&modbus->function, 10, 0, (const char *)ptr) < 0) {
-            SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
-                       "modbus function: %s", (const char *)ptr);
-            goto error;
-        }
-        /* Function code 0 is managed by decoder_event INVALID_FUNCTION_CODE */
-        if (modbus->function == MODBUS_FUNC_NONE) {
-            SCLogError(SC_ERR_INVALID_SIGNATURE,
-                    "Invalid argument \"%d\" supplied to modbus function keyword.",
-                    modbus->function);
-            goto error;
-        }
-
-        SCLogDebug("will look for modbus function %d", modbus->function);
-
-        if (ret > 2) {
-            res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 3, arg, MAX_SUBSTRINGS);
-            if (res < 0) {
-                SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-                goto error;
-            }
-
-            if (StringParseUint16(&modbus->subfunction, 10, 0, (const char *)arg) < 0) {
-                SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
-                           "modbus subfunction: %s", (const char*)arg);
-                goto error;
-            }
-            modbus->has_subfunction = true;
-
-            SCLogDebug("and subfunction %d", modbus->subfunction);
-        }
-    } else {
-        uint8_t neg = 0;
-
-        if (ptr[0] == '!') {
-            neg = 1;
-            ptr++;
-        }
-
-        if (strcmp("assigned", ptr) == 0)
-            modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED;
-        else if (strcmp("unassigned", ptr) == 0)
-            modbus->category = MODBUS_CAT_PUBLIC_UNASSIGNED;
-        else if (strcmp("public", ptr) == 0)
-            modbus->category = MODBUS_CAT_PUBLIC_ASSIGNED | MODBUS_CAT_PUBLIC_UNASSIGNED;
-        else if (strcmp("user", ptr) == 0)
-            modbus->category = MODBUS_CAT_USER_DEFINED;
-        else if (strcmp("reserved", ptr) == 0)
-            modbus->category = MODBUS_CAT_RESERVED;
-        else if (strcmp("all", ptr) == 0)
-            modbus->category = MODBUS_CAT_ALL;
-
-        if (neg)
-            modbus->category = ~modbus->category;
-        SCLogDebug("will look for modbus category function %d", modbus->category);
-    }
-
-    SCReturnPtr(modbus, "DetectModbusFunction");
-
-error:
-    if (modbus != NULL)
-        DetectModbusFree(de_ctx, modbus);
-
-    SCReturnPtr(NULL, "DetectModbus");
-}
-
-/** \internal
- *
- * \brief This function is used to parse Modbus parameters in unit id mode
- *
- * \param str Pointer to the user provided id option
- *
- * \retval Pointer to DetectModbusUnit on success or NULL on failure
- */
-static DetectModbus *DetectModbusUnitIdParse(DetectEngineCtx *de_ctx, const char *str)
-{
-    SCEnter();
-    DetectModbus *modbus = NULL;
-
-    char    arg[MAX_SUBSTRINGS];
-    int     ov[MAX_SUBSTRINGS], ret, res;
-
-    ret = DetectParsePcreExec(&unit_id_parse_regex, str, 0, 0, ov, MAX_SUBSTRINGS);
-    if (ret < 1)
-        goto error;
-
-    res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 1, arg, MAX_SUBSTRINGS);
-    if (res < 0) {
-        SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-        goto error;
-    }
-
-    if (ret > 3) {
-        /* We have more Modbus option */
-        const char *str_ptr;
-
-        res = pcre_get_substring((char *)str, ov, MAX_SUBSTRINGS, 4, &str_ptr);
-        if (res < 0) {
-            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-            goto error;
-        }
-
-        if ((modbus = DetectModbusFunctionParse(de_ctx, str_ptr)) == NULL) {
-            if ((modbus = DetectModbusAccessParse(de_ctx, str_ptr)) == NULL) {
-                SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
-                goto error;
-            }
-        }
-    } else {
-        /* We have only unit id Modbus option */
-        modbus = (DetectModbus *) SCCalloc(1, sizeof(DetectModbus));
-        if (unlikely(modbus == NULL))
-            goto error;
-    }
-
-    /* We have a correct unit id option */
-    modbus->unit_id = (DetectModbusValue *) SCCalloc(1, sizeof(DetectModbusValue));
-    if (unlikely(modbus->unit_id == NULL))
-        goto error;
-
-    uint8_t idx;
-    if (arg[0] == '>') {
-        idx = 1;
-        modbus->unit_id->mode  = DETECT_MODBUS_GT;
-    } else if (arg[0] == '<') {
-        idx = 1;
-        modbus->unit_id->mode  = DETECT_MODBUS_LT;
-    } else {
-        idx = 0;
-    }
-    if (StringParseUint16(&modbus->unit_id->min, 10, 0, (const char *) (arg + idx)) < 0) {
-        SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
-                   "modbus min unit id: %s", (const char*)(arg + idx));
-        goto error;
-    }
-    SCLogDebug("and min/equal unit id %d", modbus->unit_id->min);
-
-    if (ret > 2) {
-        res = pcre_copy_substring(str, ov, MAX_SUBSTRINGS, 2, arg, MAX_SUBSTRINGS);
-        if (res < 0) {
-            SCLogError(SC_ERR_PCRE_GET_SUBSTRING, "pcre_get_substring failed");
-            goto error;
-        }
-
-        if (*arg != '\0') {
-            if (StringParseUint16(&modbus->unit_id->max, 10, 0, (const char *) (arg + 2)) < 0) {
-                SCLogError(SC_ERR_INVALID_VALUE, "Invalid value for "
-                           "modbus max unit id: %s", (const char*)(arg + 2));
-                goto error;
-            }
-            modbus->unit_id->mode  = DETECT_MODBUS_RA;
-            SCLogDebug("and max unit id %d", modbus->unit_id->max);
-        }
+    if (ptr != NULL) {
+        rs_modbus_free(ptr);
     }
-
-    SCReturnPtr(modbus, "DetectModbusUnitId");
-
-error:
-    if (modbus != NULL)
-        DetectModbusFree(de_ctx, modbus);
-
-    SCReturnPtr(NULL, "DetectModbus");
+    SCReturn;
 }
 
-
 /** \internal
  *
  * \brief this function is used to add the parsed "id" option into the current signature
@@ -509,19 +90,15 @@ error:
 static int DetectModbusSetup(DetectEngineCtx *de_ctx, Signature *s, const char *str)
 {
     SCEnter();
-    DetectModbus    *modbus = NULL;
+    DetectModbusRust *modbus = NULL;
     SigMatch        *sm = NULL;
 
     if (DetectSignatureSetAppProto(s, ALPROTO_MODBUS) != 0)
         return -1;
 
-    if ((modbus = DetectModbusUnitIdParse(de_ctx, str)) == NULL) {
-        if ((modbus = DetectModbusFunctionParse(de_ctx, str)) == NULL) {
-            if ((modbus = DetectModbusAccessParse(de_ctx, str)) == NULL) {
-                SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
-                goto error;
-            }
-        }
+    if ((modbus = rs_modbus_parse(str)) == NULL) {
+        SCLogError(SC_ERR_PCRE_MATCH, "invalid modbus option");
+        goto error;
     }
 
     /* Okay so far so good, lets get this into a SigMatch and put it in the Signature. */
@@ -544,24 +121,47 @@ error:
     SCReturnInt(-1);
 }
 
+static int DetectModbusMatch(DetectEngineThreadCtx *det_ctx, Flow *f, uint8_t flags, void *state,
+        void *txv, const Signature *s, const SigMatchCtx *ctx)
+{
+    return rs_modbus_inspect(txv, (void *)ctx);
+}
+
+/** \brief Do the content inspection & validation for a signature
+ *
+ *  \param de_ctx   Detection engine context
+ *  \param det_ctx  Detection engine thread context
+ *  \param s        Signature to inspect ( and sm: SigMatch to inspect)
+ *  \param f        Flow
+ *  \param flags    App layer flags
+ *  \param alstate  App layer state
+ *  \param txv      Pointer to Modbus Transaction structure
+ *
+ *  \retval 0 no match or 1 match
+ */
+static int DetectEngineInspectModbus(DetectEngineCtx *de_ctx, DetectEngineThreadCtx *det_ctx,
+        const struct DetectEngineAppInspectionEngine_ *engine, const Signature *s, Flow *f,
+        uint8_t flags, void *alstate, void *txv, uint64_t tx_id)
+{
+    return DetectEngineInspectGenericList(
+            de_ctx, det_ctx, s, engine->smd, f, flags, alstate, txv, tx_id);
+}
+
 /**
  * \brief Registration function for Modbus keyword
  */
 void DetectModbusRegister(void)
 {
-    SCEnter();
-    sigmatch_table[DETECT_AL_MODBUS].name          = "modbus";
-    sigmatch_table[DETECT_AL_MODBUS].desc          = "match on various properties of Modbus requests";
-    sigmatch_table[DETECT_AL_MODBUS].url           = "/rules/modbus-keyword.html#modbus-keyword";
-    sigmatch_table[DETECT_AL_MODBUS].Match         = NULL;
-    sigmatch_table[DETECT_AL_MODBUS].Setup         = DetectModbusSetup;
-    sigmatch_table[DETECT_AL_MODBUS].Free          = DetectModbusFree;
+    sigmatch_table[DETECT_AL_MODBUS].name = "modbus";
+    sigmatch_table[DETECT_AL_MODBUS].desc = "match on various properties of Modbus requests";
+    sigmatch_table[DETECT_AL_MODBUS].url = "/rules/modbus-keyword.html#modbus-keyword";
+    sigmatch_table[DETECT_AL_MODBUS].Match = NULL;
+    sigmatch_table[DETECT_AL_MODBUS].Setup = DetectModbusSetup;
+    sigmatch_table[DETECT_AL_MODBUS].Free = DetectModbusFree;
+    sigmatch_table[DETECT_AL_MODBUS].AppLayerTxMatch = DetectModbusMatch;
 #ifdef UNITTESTS
     sigmatch_table[DETECT_AL_MODBUS].RegisterTests = DetectModbusRegisterTests;
 #endif
-    DetectSetupParseRegexes(PARSE_REGEX_UNIT_ID, &unit_id_parse_regex);
-    DetectSetupParseRegexes(PARSE_REGEX_FUNCTION, &function_parse_regex);
-    DetectSetupParseRegexes(PARSE_REGEX_ACCESS, &access_parse_regex);
 
     DetectAppLayerInspectEngineRegister2(
             "modbus", ALPROTO_MODBUS, SIG_FLAG_TOSERVER, 0, DetectEngineInspectModbus, NULL);
@@ -572,6 +172,72 @@ void DetectModbusRegister(void)
 #ifdef UNITTESTS /* UNITTESTS */
 #include "util-unittest.h"
 
+/** Convert rust structure to C for regression tests.
+ *
+ * Note: Newly allocated `DetectModbus` structure must be freed.
+ *
+ * TODO: remove this after regression testing commit.
+ */
+static DetectModbusValue *DetectModbusValueRustToC(uint16_t min, uint16_t max)
+{
+    DetectModbusValue *value = SCMalloc(sizeof(*value));
+    FAIL_IF_NULL(value);
+
+    value->min = min;
+    value->max = max;
+
+    if (min == max) {
+        value->mode = DETECT_MODBUS_EQ;
+    } else if (min == 0) {
+        value->mode = DETECT_MODBUS_LT;
+    } else if (max == UINT16_MAX) {
+        value->mode = DETECT_MODBUS_GT;
+    } else {
+        value->mode = DETECT_MODBUS_RA;
+    }
+
+    return value;
+}
+
+static DetectModbus *DetectModbusRustToC(DetectModbusRust *ctx)
+{
+    DetectModbus *modbus = SCMalloc(sizeof(*modbus));
+    FAIL_IF_NULL(modbus);
+
+    modbus->category = rs_modbus_get_category(ctx);
+    modbus->function = rs_modbus_get_function(ctx);
+    modbus->subfunction = rs_modbus_get_subfunction(ctx);
+    modbus->has_subfunction = rs_modbus_get_has_subfunction(ctx);
+    modbus->type = rs_modbus_get_access_type(ctx);
+
+    modbus->unit_id = DetectModbusValueRustToC(
+            rs_modbus_get_unit_id_min(ctx), rs_modbus_get_unit_id_max(ctx));
+
+    modbus->address = DetectModbusValueRustToC(
+            rs_modbus_get_address_min(ctx), rs_modbus_get_address_max(ctx));
+
+    modbus->data =
+            DetectModbusValueRustToC(rs_modbus_get_data_min(ctx), rs_modbus_get_data_max(ctx));
+
+    return modbus;
+}
+
+static void DetectModbusCFree(DetectModbus *modbus)
+{
+    if (modbus) {
+        if (modbus->unit_id)
+            SCFree(modbus->unit_id);
+
+        if (modbus->address)
+            SCFree(modbus->address);
+
+        if (modbus->data)
+            SCFree(modbus->data);
+
+        SCFree(modbus);
+    }
+}
+
 /** \test Signature containing a function. */
 static int DetectModbusTest01(void)
 {
@@ -590,10 +256,12 @@ static int DetectModbusTest01(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT(modbus->function == 1);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -618,11 +286,13 @@ static int DetectModbusTest02(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT(modbus->function == 8);
     FAIL_IF_NOT(modbus->subfunction == 4);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -647,10 +317,12 @@ static int DetectModbusTest03(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT(modbus->category == MODBUS_CAT_RESERVED);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -663,8 +335,6 @@ static int DetectModbusTest04(void)
     DetectEngineCtx *de_ctx = NULL;
     DetectModbus    *modbus = NULL;
 
-    uint8_t category = ~MODBUS_CAT_PUBLIC_ASSIGNED;
-
     de_ctx = DetectEngineCtxInit();
     FAIL_IF_NULL(de_ctx);
 
@@ -677,10 +347,15 @@ static int DetectModbusTest04(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    FAIL_IF_NOT(modbus->category == category);
+    FAIL_IF(modbus->category & MODBUS_CAT_PUBLIC_ASSIGNED);
+    FAIL_IF_NOT(modbus->category & MODBUS_CAT_PUBLIC_UNASSIGNED);
+    FAIL_IF_NOT(modbus->category & MODBUS_CAT_USER_DEFINED);
+    FAIL_IF_NOT(modbus->category & MODBUS_CAT_RESERVED);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -705,10 +380,12 @@ static int DetectModbusTest05(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT(modbus->type == MODBUS_TYP_READ);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -735,10 +412,12 @@ static int DetectModbusTest06(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT(modbus->type == type);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -766,12 +445,14 @@ static int DetectModbusTest07(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT(modbus->type == type);
     FAIL_IF_NOT((*modbus->address).mode == mode);
     FAIL_IF_NOT((*modbus->address).min == 1000);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -799,12 +480,14 @@ static int DetectModbusTest08(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT(modbus->type == type);
     FAIL_IF_NOT((*modbus->address).mode == mode);
     FAIL_IF_NOT((*modbus->address).min == 500);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -833,7 +516,8 @@ static int DetectModbusTest09(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT(modbus->type == type);
     FAIL_IF_NOT((*modbus->address).mode == addressMode);
@@ -842,6 +526,7 @@ static int DetectModbusTest09(void)
     FAIL_IF_NOT((*modbus->data).min == 500);
     FAIL_IF_NOT((*modbus->data).max == 1000);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -867,11 +552,13 @@ static int DetectModbusTest10(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT((*modbus->unit_id).min == 10);
     FAIL_IF_NOT((*modbus->unit_id).mode == mode);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -897,13 +584,15 @@ static int DetectModbusTest11(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT((*modbus->unit_id).min == 10);
     FAIL_IF_NOT((*modbus->unit_id).mode == mode);
     FAIL_IF_NOT(modbus->function == 8);
     FAIL_IF_NOT(modbus->subfunction == 4);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -931,7 +620,8 @@ static int DetectModbusTest12(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT((*modbus->unit_id).min == 10);
     FAIL_IF_NOT((*modbus->unit_id).mode == mode);
@@ -939,6 +629,7 @@ static int DetectModbusTest12(void)
     FAIL_IF_NOT((*modbus->address).mode == mode);
     FAIL_IF_NOT((*modbus->address).min == 1000);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
@@ -964,12 +655,14 @@ static int DetectModbusTest13(void)
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]);
     FAIL_IF_NULL(de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
-    modbus = (DetectModbus *) de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx;
+    modbus = DetectModbusRustToC(
+            (DetectModbusRust *)de_ctx->sig_list->sm_lists_tail[g_modbus_buffer_id]->ctx);
 
     FAIL_IF_NOT((*modbus->unit_id).min == 10);
     FAIL_IF_NOT((*modbus->unit_id).max == 500);
     FAIL_IF_NOT((*modbus->unit_id).mode == mode);
 
+    DetectModbusCFree(modbus);
     SigGroupCleanup(de_ctx);
     SigCleanSignatures(de_ctx);
     DetectEngineCtxFree(de_ctx);
index 1150b4861dff7dd41b3f25abfea5fcdfc6a903b4..bcae579af7f42887d6bc67736056c7af17c088da 100644 (file)
 #include "app-layer-ssh.h"
 #include "app-layer-ftp.h"
 #include "app-layer-smtp.h"
-#include "app-layer-modbus.h"
 #include "app-layer-enip.h"
 #include "app-layer-dnp3.h"
 #include "app-layer-smb.h"