summaryrefslogtreecommitdiffstats
path: root/rust/src/modbus/modbus.rs
diff options
context:
space:
mode:
Diffstat (limited to 'rust/src/modbus/modbus.rs')
-rw-r--r--rust/src/modbus/modbus.rs1458
1 files changed, 1458 insertions, 0 deletions
diff --git a/rust/src/modbus/modbus.rs b/rust/src/modbus/modbus.rs
new file mode 100644
index 0000000..246e9ca
--- /dev/null
+++ b/rust/src/modbus/modbus.rs
@@ -0,0 +1,1458 @@
+/* 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::{self, *};
+use crate::core::{self, AppProto, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP};
+
+use std::ffi::CString;
+
+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 { probe_strict: true };
+
+static mut ALPROTO_MODBUS: AppProto = ALPROTO_UNKNOWN;
+
+#[derive(AppLayerEvent)]
+enum ModbusEvent {
+ UnsolicitedResponse,
+ InvalidFunctionCode,
+ InvalidLength,
+ InvalidValue,
+ InvalidExceptionCode,
+ ValueMismatch,
+ Flooded,
+ InvalidProtocolId,
+}
+pub struct ModbusTransaction {
+ pub id: u64,
+
+ pub request: Option<Message>,
+ pub response: Option<Message>,
+
+ pub tx_data: AppLayerTxData,
+}
+
+impl Transaction for ModbusTransaction {
+ fn id(&self) -> u64 {
+ self.id
+ }
+}
+
+impl ModbusTransaction {
+ pub fn new(id: u64) -> Self {
+ Self {
+ id,
+ request: None,
+ response: None,
+ tx_data: AppLayerTxData::new(),
+ }
+ }
+
+ fn set_event(&mut self, event: ModbusEvent) {
+ self.tx_data.set_event(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);
+ }
+ }
+}
+
+#[derive(Default)]
+pub struct ModbusState {
+ state_data: AppLayerStateData,
+ pub transactions: Vec<ModbusTransaction>,
+ tx_id: u64,
+ givenup: bool, // Indicates flood
+}
+
+impl State<ModbusTransaction> for ModbusState {
+ fn get_transaction_count(&self) -> usize {
+ self.transactions.len()
+ }
+
+ fn get_transaction_by_index(&self, index: usize) -> Option<&ModbusTransaction> {
+ self.transactions.get(index)
+ }
+}
+
+impl ModbusState {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut ModbusTransaction> {
+ self.transactions.iter_mut().find(|tx| tx.id == tx_id + 1)
+ }
+
+ /// 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.is_empty() {
+ 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 unsafe 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 unsafe 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,
+ stream_slice: StreamSlice,
+ _data: *const std::os::raw::c_void,
+) -> AppLayerResult {
+ let buf = stream_slice.as_slice();
+ if buf.is_empty() {
+ if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0 {
+ return AppLayerResult::ok();
+ } else {
+ return AppLayerResult::err();
+ }
+ }
+
+ let state = cast_pointer!(state, ModbusState);
+ state.parse(buf, Direction::ToServer)
+}
+
+#[no_mangle]
+pub unsafe 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,
+ stream_slice: StreamSlice,
+ _data: *const std::os::raw::c_void,
+) -> AppLayerResult {
+ let buf = stream_slice.as_slice();
+ if buf.is_empty() {
+ if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 {
+ return AppLayerResult::ok();
+ } else {
+ return AppLayerResult::err();
+ }
+ }
+
+ let state = cast_pointer!(state, ModbusState);
+ state.parse(buf, Direction::ToClient)
+}
+
+#[no_mangle]
+pub unsafe 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 unsafe 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 unsafe 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 unsafe 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
+}
+
+export_state_data_get!(rs_modbus_get_state_data, ModbusState);
+
+#[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_eventinfo: Some(ModbusEvent::get_event_info),
+ get_eventinfo_byid: Some(ModbusEvent::get_event_info_by_id),
+ localstorage_new: None,
+ localstorage_free: None,
+ get_tx_files: None,
+ get_tx_iterator: Some(applayer::state_get_tx_iterator::<ModbusState, ModbusTransaction>),
+ get_tx_data: rs_modbus_state_get_tx_data,
+ get_state_data: rs_modbus_get_state_data,
+ apply_tx_config: None,
+ flags: 0,
+ truncate: None,
+ get_frame_id_by_name: None,
+ get_frame_name_by_id: None,
+ };
+
+ let ip_proto_str = CString::new("tcp").unwrap();
+ if AppLayerProtoDetectConfProtoDetectionEnabledDefault(ip_proto_str.as_ptr(), parser.name, false) != 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 unsafe 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 unsafe 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, 12),
+ 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![]));
+ }
+}