use crate::applayer::*;
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; pub const MODBUS_PARSER: sawp_modbus::Modbus = sawp_modbus::Modbus {};
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 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, }
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
}
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
}
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> {
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);
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()
}
}
#[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,
input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
) -> AppLayerResult {
if input_len == 0 {
if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0 {
return AppLayerResult::ok();
} else {
return AppLayerResult::err();
}
}
let state = cast_pointer!(state, ModbusState);
let buf = std::slice::from_raw_parts(input, input_len as usize);
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,
input: *const u8, input_len: u32, _data: *const std::os::raw::c_void, _flags: u8,
) -> AppLayerResult {
if input_len == 0 {
if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 {
return AppLayerResult::ok();
} else {
return AppLayerResult::err();
}
}
let state = cast_pointer!(state, ModbusState);
let buf = std::slice::from_raw_parts(input, input_len as usize);
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_events(
tx: *mut std::os::raw::c_void,
) -> *mut core::AppLayerDecoderEvents {
let tx = cast_pointer!(tx, ModbusTransaction);
tx.events
}
#[no_mangle]
pub unsafe 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 unsafe 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 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
}
#[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(ModbusEvent::get_event_info),
get_eventinfo_byid: Some(ModbusEvent::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: 0,
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);
}
}
}
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, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, ];
const RD_COILS_REQ: &[u8] = &[
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x78, 0x90, 0x00, 0x13, ];
const RD_COILS_RESP: &[u8] = &[
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x01, 0x03, 0xCD, 0x6B, 0x05, ];
const RD_COILS_ERR_RESP: &[u8] = &[
0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x81, 0xFF, ];
const WR_SINGLE_REG_REQ: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x01, 0x00, 0x03, ];
const INVALID_WR_SINGLE_REG_REQ: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x04, 0x00, 0x06, 0x00, 0x01, ];
const WR_SINGLE_REG_RESP: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x06, 0x00, 0x01, 0x00, 0x03, ];
const WR_MULT_REG_REQ: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x10, 0x00, 0x01, 0x00, 0x02, 0x04, 0x00, 0x0A, 0x01, 0x02,
];
const INVALID_PDU_WR_MULT_REG_REQ: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x10, ];
const WR_MULT_REG_RESP: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x10, 0x00, 0x01, 0x00, 0x02, ];
const MASK_WR_REG_REQ: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x16, 0x00, 0x04, 0x00, 0xF2, 0x00, 0x25, ];
const INVALID_MASK_WR_REG_REQ: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x16, 0x00, 0x04, 0x00, 0xF2, ];
const MASK_WR_REG_RESP: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x08, 0x00, 0x16, 0x00, 0x04, 0x00, 0xF2, 0x00, 0x25, ];
const RD_WR_MULT_REG_REQ: &[u8] = &[
0x12, 0x34, 0x00, 0x00, 0x00, 0x11, 0x00, 0x17, 0x00, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x03, 0x06, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC,
];
const RD_WR_MULT_REG_RESP: &[u8] = &[
0x12, 0x34, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x17, 0x0B, 0x00, 0xFE, 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00,
];
const FORCE_LISTEN_ONLY_MODE: &[u8] = &[
0x0A, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x00, ];
const INVALID_PROTO_REQ: &[u8] = &[
0x00, 0x00, 0x00, 0x01, 0x00, 0x06, 0x00, 0x01, 0x78, 0x90, 0x00, 0x13, ];
const INVALID_LEN_WR_MULT_REG_REQ: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x09, 0x00, 0x10, 0x00, 0x01, 0x00, 0x02, 0x04, 0x00, 0x0A, 0x01, 0x02,
];
const EXCEEDED_LEN_WR_MULT_REG_REQ: &[u8] = &[
0x00, 0x0A, 0x00, 0x00, 0xff, 0xfa, 0x00, 0x10, 0x00, 0x01, 0x7f, 0xf9, 0xff, ];
#[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![]));
}
}