use crate::applayer::{self, *};
use crate::core::*;
use crate::flow::Flow;
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};
use suricata_sys::sys::{
AppLayerParserState, AppProto, SCAppLayerParserConfParserEnabled,
SCAppLayerParserRegisterLogger, SCAppLayerParserStateIssetFlag,
SCAppLayerProtoDetectConfProtoDetectionEnabledDefault,
};
pub const REQUEST_FLOOD: usize = 500; 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, }
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)
}
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) {
tx.tx_data.updated_tc = true;
tx.tx_data.updated_ts = true;
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) {
tx.tx_data.updated_tc = true;
tx.tx_data.updated_ts = true;
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, flow: *mut Flow, 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.tx_data.updated_tc = true;
tx.tx_data.updated_ts = true;
tx.request = Some(msg);
if !flow.is_null() {
sc_app_layer_parser_trigger_raw_stream_inspection(
flow,
Direction::ToServer as i32,
);
}
}
None => {
let mut tx = match self.new_tx() {
Some(tx) => tx,
None => return AppLayerResult::err(),
};
tx.set_events_from_flags(&msg.error_flags);
tx.request = Some(msg);
self.transactions.push(tx);
if !flow.is_null() {
sc_app_layer_parser_trigger_raw_stream_inspection(
flow,
Direction::ToServer as i32,
);
}
}
}
}
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.tx_data.updated_tc = true;
tx.tx_data.updated_ts = true;
tx.response = Some(msg);
if !flow.is_null() {
sc_app_layer_parser_trigger_raw_stream_inspection(
flow,
Direction::ToClient as i32,
);
}
}
None => {
let mut tx = match self.new_tx() {
Some(tx) => tx,
None => return AppLayerResult::err(),
};
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 !flow.is_null() {
sc_app_layer_parser_trigger_raw_stream_inspection(
flow,
Direction::ToClient as i32,
);
}
}
},
}
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()
}
}
extern "C" fn modbus_probe(
_flow: *const Flow, _direction: u8, input: *const u8, len: u32, _rdir: *mut u8,
) -> AppProto {
if input.is_null() {
return ALPROTO_UNKNOWN;
}
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 => ALPROTO_FAILED,
}
}
extern "C" fn 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
}
extern "C" fn modbus_state_free(state: *mut std::os::raw::c_void) {
let _state: Box<ModbusState> = unsafe { Box::from_raw(state as *mut ModbusState) };
}
unsafe extern "C" fn 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);
}
unsafe extern "C" fn modbus_parse_request(
flow: *mut Flow, state: *mut std::os::raw::c_void, pstate: *mut AppLayerParserState,
stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
) -> AppLayerResult {
let buf = stream_slice.as_slice();
if buf.is_empty() {
if SCAppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0 {
return AppLayerResult::ok();
} else {
return AppLayerResult::err();
}
}
let state = cast_pointer!(state, ModbusState);
state.parse(flow, buf, Direction::ToServer)
}
unsafe extern "C" fn modbus_parse_response(
flow: *mut Flow, state: *mut std::os::raw::c_void, pstate: *mut AppLayerParserState,
stream_slice: StreamSlice, _data: *const std::os::raw::c_void,
) -> AppLayerResult {
let buf = stream_slice.as_slice();
if buf.is_empty() {
if SCAppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 {
return AppLayerResult::ok();
} else {
return AppLayerResult::err();
}
}
let state = cast_pointer!(state, ModbusState);
state.parse(flow, buf, Direction::ToClient)
}
#[no_mangle]
pub unsafe extern "C" fn SCModbusStateGetTxCount(state: *mut std::os::raw::c_void) -> u64 {
let state = cast_pointer!(state, ModbusState);
state.tx_id
}
unsafe extern "C" fn 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(),
}
}
unsafe extern "C" fn 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
}
unsafe extern "C" fn 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!(modbus_get_state_data, ModbusState);
#[no_mangle]
pub unsafe extern "C" fn SCRegisterModbusParser() {
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(modbus_probe),
probe_tc: Some(modbus_probe),
min_depth: 0,
max_depth: 16,
state_new: modbus_state_new,
state_free: modbus_state_free,
tx_free: modbus_state_tx_free,
parse_ts: modbus_parse_request,
parse_tc: modbus_parse_response,
get_tx_count: SCModbusStateGetTxCount,
get_tx: modbus_state_get_tx,
tx_comp_st_ts: 1,
tx_comp_st_tc: 1,
tx_get_progress: 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: modbus_state_get_tx_data,
get_state_data: modbus_get_state_data,
apply_tx_config: None,
flags: 0,
get_frame_id_by_name: None,
get_frame_name_by_id: None,
get_state_id_by_name: None,
get_state_name_by_id: None,
};
let ip_proto_str = CString::new("tcp").unwrap();
if SCAppLayerProtoDetectConfProtoDetectionEnabledDefault(
ip_proto_str.as_ptr(),
parser.name,
false,
) != 0
{
let alproto = AppLayerRegisterProtocolDetection(&parser, 1);
ALPROTO_MODBUS = alproto;
if SCAppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 {
let _ = AppLayerRegisterParser(&parser, alproto);
}
SCAppLayerParserRegisterLogger(IPPROTO_TCP, ALPROTO_MODBUS);
}
}
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 SCModbusMessageGetFunction(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 SCModbusMessageGetSubfunction(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 SCModbusMessageGetReadRequestAddress(
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 SCModbusMessageGetReadRequestQuantity(
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 SCModbusMessageGetRwMultreqReadAddress(
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 SCModbusMessageGetRwMultreqReadQuantity(
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 SCModbusMessageGetRwMultreqWriteAddress(
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 SCModbusMessageGetRwMultreqWriteQuantity(
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 SCModbusMessageGetRwMultreqWriteData(
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 SCModbusMessageGetWriteMultreqAddress(
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 SCModbusMessageGetWriteMultreqQuantity(
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 SCModbusMessageGetWriteMultreqData(
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 SCModbusMessageGetAndMask(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 SCModbusMessageGetOrMask(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 SCModbusMessageGetWriteAddress(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 SCModbusMessageGetWriteData(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 SCModbusMessageGetBytevecData(
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 SCModbusStateGetTxRequest(
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 SCModbusStateGetTxResponse(
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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(
std::ptr::null_mut(),
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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(
std::ptr::null_mut(),
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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(
std::ptr::null_mut(),
&RD_COILS_REQ[0..(RD_COILS_REQ.len() - 3)],
Direction::ToServer
)
);
assert_eq!(state.transactions.len(), 0);
assert_eq!(
AppLayerResult::ok(),
state.parse(std::ptr::null_mut(), 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(std::ptr::null_mut(), &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(std::ptr::null_mut(), &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(
std::ptr::null_mut(),
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(
std::ptr::null_mut(),
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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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(
std::ptr::null_mut(),
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(std::ptr::null_mut(), 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(
std::ptr::null_mut(),
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(std::ptr::null_mut(), 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(std::ptr::null_mut(), 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![]));
}
}