use super::modbus::ModbusTransaction;
use lazy_static::lazy_static;
use regex::Regex;
use sawp_modbus::{AccessType, CodeCategory, Data, Flags, FunctionCode, Message};
use std::ffi::CStr;
use std::ops::{Range, RangeInclusive};
use std::os::raw::{c_char, c_void};
use std::str::FromStr;
lazy_static! {
static ref ACCESS_RE: Regex = Regex::new(
"^[[:space:]]*\"?[[:space:]]*access[[:space:]]*(read|write)\
[[:space:]]*(discretes|coils|input|holding)?\
(?:,[[:space:]]*address[[:space:]]+([<>]?[[:digit:]]+)(?:<>([[:digit:]]+))?\
(?:,[[:space:]]*value[[:space:]]+([<>]?[[:digit:]]+)(?:<>([[:digit:]]+))?)?)?\
[[:space:]]*\"?[[:space:]]*$"
)
.unwrap();
static ref FUNC_RE: Regex = Regex::new(
"^[[:space:]]*\"?[[:space:]]*function[[:space:]]*(!?[A-z0-9]+)\
(?:,[[:space:]]*subfunction[[:space:]]+([[:digit:]]+))?[[:space:]]*\"?[[:space:]]*$"
)
.unwrap();
static ref UNIT_RE: Regex = Regex::new(
"^[[:space:]]*\"?[[:space:]]*unit[[:space:]]+([<>]?[[:digit:]]+)\
(?:<>([[:digit:]]+))?(?:,[[:space:]]*(.*))?[[:space:]]*\"?[[:space:]]*$"
)
.unwrap();
}
#[derive(Debug, PartialEq, Default)]
pub struct DetectModbusRust {
category: Option<Flags<CodeCategory>>,
function: Option<FunctionCode>,
subfunction: Option<u16>,
access_type: Option<Flags<AccessType>>,
unit_id: Option<Range<u16>>,
address: Option<Range<u16>>,
value: Option<Range<u16>>,
}
fn check_match_range(sig_range: &Range<u16>, trans_range: RangeInclusive<u16>) -> bool {
if sig_range.start == sig_range.end {
sig_range.start >= *trans_range.start() && sig_range.start <= *trans_range.end()
} else if sig_range.start == u16::MIN {
sig_range.end > *trans_range.start()
} else if sig_range.end == u16::MAX {
sig_range.start < *trans_range.end()
} else {
sig_range.start < *trans_range.end() && *trans_range.start() < sig_range.end
}
}
fn check_match(sig_range: &Range<u16>, value: u16) -> bool {
if sig_range.start == sig_range.end {
sig_range.start == value
} else if sig_range.start == u16::MIN {
sig_range.end > value
} else if sig_range.end == u16::MAX {
sig_range.start < value
} else {
sig_range.start < value && value < sig_range.end
}
}
fn parse_range(min_str: &str, max_str: &str) -> Result<Range<u16>, ()> {
if max_str.is_empty() {
if let Some(sign) = min_str.chars().next() {
debug_validate_bug_on!(!sign.is_ascii_digit() && sign != '<' && sign != '>');
match min_str[!sign.is_ascii_digit() as usize..].parse::<u16>() {
Ok(num) => match sign {
'>' => Ok(num..u16::MAX),
'<' => Ok(u16::MIN..num),
_ => Ok(num..num),
},
Err(_) => {
SCLogError!("Invalid min number: {}", min_str);
Err(())
}
}
} else {
Err(())
}
} else {
let min = match min_str.parse::<u16>() {
Ok(num) => num,
Err(_) => {
SCLogError!("Invalid min number: {}", min_str);
return Err(());
}
};
let max = match max_str.parse::<u16>() {
Ok(num) => num,
Err(_) => {
SCLogError!("Invalid max number: {}", max_str);
return Err(());
}
};
Ok(min..max)
}
}
#[no_mangle]
pub unsafe extern "C" fn SCModbusParse(c_arg: *const c_char) -> *mut c_void {
if c_arg.is_null() {
return std::ptr::null_mut();
}
if let Ok(arg) = CStr::from_ptr(c_arg).to_str() {
match parse_unit_id(arg)
.or_else(|_| parse_function(arg))
.or_else(|_| parse_access(arg))
{
Ok(detect) => return Box::into_raw(Box::new(detect)) as *mut c_void,
Err(()) => return std::ptr::null_mut(),
}
}
std::ptr::null_mut()
}
#[no_mangle]
pub unsafe extern "C" fn SCModbusFree(ptr: *mut c_void) {
if !ptr.is_null() {
let _ = Box::from_raw(ptr as *mut DetectModbusRust);
}
}
#[no_mangle]
pub extern "C" fn SCModbusInspect(tx: &ModbusTransaction, modbus: &DetectModbusRust) -> u8 {
let msg = match &tx.request {
Some(r) => r,
None => match &tx.response {
Some(r) => r,
None => return 0,
},
};
if let Some(unit_id) = &modbus.unit_id {
if !check_match(unit_id, msg.unit_id.into()) {
return 0;
}
}
if let Some(access_type) = &modbus.access_type {
let rd_wr_access = *access_type & (AccessType::READ | AccessType::WRITE);
let access_func = *access_type & AccessType::FUNC_MASK;
if rd_wr_access.is_empty()
|| !msg.access_type.intersects(rd_wr_access)
|| (!access_func.is_empty() && !msg.access_type.intersects(access_func))
{
return 0;
}
return inspect_data(msg, modbus) as u8;
}
if let Some(category) = modbus.category {
return u8::from(msg.category.intersects(category));
}
match &modbus.function {
Some(func) if func == &msg.function.code => match modbus.subfunction {
Some(subfunc) => {
if let Data::Diagnostic { func, data: _ } = &msg.data {
u8::from(subfunc == func.raw)
} else {
0
}
}
None => 1,
},
None => 1,
_ => 0,
}
}
fn inspect_data(msg: &Message, modbus: &DetectModbusRust) -> bool {
let sig_address = if let Some(sig_addr) = &modbus.address {
if let Some(req_addr) = msg.get_address_range() {
if !check_match_range(sig_addr, req_addr) {
return false;
}
} else {
return false;
}
sig_addr.start
} else {
return true;
};
let sig_value = if let Some(value) = &modbus.value {
value
} else {
return true;
};
if let Some(value) = msg.get_write_value_at_address(sig_address) {
check_match(sig_value, value)
} else {
false
}
}
fn parse_access(access_str: &str) -> Result<DetectModbusRust, ()> {
let re = if let Some(re) = ACCESS_RE.captures(access_str) {
re
} else {
return Err(());
};
let mut access_type: Flags<AccessType> = match re.get(1) {
Some(access) => match AccessType::from_str(access.as_str()) {
Ok(access_type) => access_type.into(),
Err(_) => {
SCLogError!("Unknown access keyword {}", access.as_str());
return Err(());
}
},
None => {
SCLogError!("No access keyword found");
return Err(());
}
};
access_type = match re.get(2) {
Some(x) if x.as_str() == "coils" => access_type | AccessType::COILS,
Some(x) if x.as_str() == "holding" => access_type | AccessType::HOLDING,
Some(x) if x.as_str() == "discretes" => {
if access_type == AccessType::WRITE {
SCLogError!("Discrete access is only read access");
return Err(());
}
access_type | AccessType::DISCRETES
}
Some(x) if x.as_str() == "input" => {
if access_type == AccessType::WRITE {
SCLogError!("Input access is only read access");
return Err(());
}
access_type | AccessType::INPUT
}
Some(unknown) => {
SCLogError!("Unknown access keyword {}", unknown.as_str());
return Err(());
}
None => access_type,
};
let address = if let Some(min) = re.get(3) {
let max_str = if let Some(max) = re.get(4) {
max.as_str()
} else {
""
};
parse_range(min.as_str(), max_str)?
} else {
return Ok(DetectModbusRust {
access_type: Some(access_type),
..Default::default()
});
};
let value = if let Some(min) = re.get(5) {
if address.start != address.end {
SCLogError!("rule contains conflicting keywords (address range and value).");
return Err(());
}
if access_type == AccessType::READ {
SCLogError!("Value keyword only works in write access");
return Err(());
}
let max_str = if let Some(max) = re.get(6) {
max.as_str()
} else {
""
};
parse_range(min.as_str(), max_str)?
} else {
return Ok(DetectModbusRust {
access_type: Some(access_type),
address: Some(address),
..Default::default()
});
};
Ok(DetectModbusRust {
access_type: Some(access_type),
address: Some(address),
value: Some(value),
..Default::default()
})
}
fn parse_function(func_str: &str) -> Result<DetectModbusRust, ()> {
let re = if let Some(re) = FUNC_RE.captures(func_str) {
re
} else {
return Err(());
};
let mut modbus: DetectModbusRust = Default::default();
if let Some(x) = re.get(1) {
let word = x.as_str();
if let Ok(num) = word.parse::<u8>() {
if num == 0 {
SCLogError!("Invalid modbus function value");
return Err(());
}
modbus.function = Some(FunctionCode::from_raw(num));
match re.get(2) {
Some(x) => {
let subfunc = x.as_str();
match subfunc.parse::<u16>() {
Ok(num) => {
modbus.subfunction = Some(num);
}
Err(_) => {
SCLogError!("Invalid subfunction value: {}", subfunc);
return Err(());
}
}
}
None => return Ok(modbus),
}
}
else {
let neg = word.starts_with('!');
let category = match &word[neg as usize..] {
"assigned" => CodeCategory::PUBLIC_ASSIGNED.into(),
"unassigned" => CodeCategory::PUBLIC_UNASSIGNED.into(),
"public" => CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED,
"user" => CodeCategory::USER_DEFINED.into(),
"reserved" => CodeCategory::RESERVED.into(),
"all" => {
CodeCategory::PUBLIC_ASSIGNED
| CodeCategory::PUBLIC_UNASSIGNED
| CodeCategory::USER_DEFINED
| CodeCategory::RESERVED
}
_ => {
SCLogError!("Keyword unknown: {}", word);
return Err(());
}
};
if neg {
modbus.category = Some(!category);
} else {
modbus.category = Some(category);
}
}
} else {
return Err(());
}
Ok(modbus)
}
fn parse_unit_id(unit_str: &str) -> Result<DetectModbusRust, ()> {
let re = if let Some(re) = UNIT_RE.captures(unit_str) {
re
} else {
return Err(());
};
let mut modbus = if let Some(x) = re.get(3) {
let extra = x.as_str();
if let Ok(mbus) = parse_function(extra) {
mbus
} else if let Ok(mbus) = parse_access(extra) {
mbus
} else {
SCLogError!("Invalid modbus option: {}", extra);
return Err(());
}
} else {
Default::default()
};
if let Some(min) = re.get(1) {
let max_str = if let Some(max) = re.get(2) {
max.as_str()
} else {
""
};
modbus.unit_id = Some(parse_range(min.as_str(), max_str)?);
} else {
SCLogError!("Min modbus unit ID not found");
return Err(());
}
Ok(modbus)
}
#[cfg(test)]
mod test {
use super::super::modbus::ModbusState;
use super::*;
use crate::applayer::*;
use sawp::parser::Direction;
#[test]
fn test_parse() {
assert_eq!(
parse_function("function 1"),
Ok(DetectModbusRust {
function: Some(FunctionCode::RdCoils),
..Default::default()
})
);
assert_eq!(
parse_function("function 8, subfunction 4"),
Ok(DetectModbusRust {
function: Some(FunctionCode::Diagnostic),
subfunction: Some(4),
..Default::default()
})
);
assert_eq!(
parse_function("function reserved"),
Ok(DetectModbusRust {
category: Some(Flags::from(CodeCategory::RESERVED)),
..Default::default()
})
);
assert_eq!(
parse_function("function !assigned"),
Ok(DetectModbusRust {
category: Some(!CodeCategory::PUBLIC_ASSIGNED),
..Default::default()
})
);
assert_eq!(
parse_access("access read"),
Ok(DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
..Default::default()
})
);
assert_eq!(
parse_access("access read discretes"),
Ok(DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::DISCRETES),
..Default::default()
})
);
assert_eq!(
parse_access("access read, address 1000"),
Ok(DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
address: Some(1000..1000),
..Default::default()
})
);
assert_eq!(
parse_access("access write coils, address <500"),
Ok(DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::COILS),
address: Some(u16::MIN..500),
..Default::default()
})
);
assert_eq!(
parse_access("access write coils, address >500"),
Ok(DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::COILS),
address: Some(500..u16::MAX),
..Default::default()
})
);
assert_eq!(
parse_access("access write holding, address 100, value <1000"),
Ok(DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(100..100),
value: Some(u16::MIN..1000),
..Default::default()
})
);
assert_eq!(
parse_access("access write holding, address 100, value 500<>1000"),
Ok(DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(100..100),
value: Some(500..1000),
..Default::default()
})
);
assert_eq!(
parse_unit_id("unit 10"),
Ok(DetectModbusRust {
unit_id: Some(10..10),
..Default::default()
})
);
assert_eq!(
parse_unit_id("unit 10, function 8, subfunction 4"),
Ok(DetectModbusRust {
function: Some(FunctionCode::Diagnostic),
subfunction: Some(4),
unit_id: Some(10..10),
..Default::default()
})
);
assert_eq!(
parse_unit_id("unit 10, access read, address 1000"),
Ok(DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
unit_id: Some(10..10),
address: Some(1000..1000),
..Default::default()
})
);
assert_eq!(
parse_unit_id("unit <11"),
Ok(DetectModbusRust {
unit_id: Some(u16::MIN..11),
..Default::default()
})
);
assert_eq!(
parse_unit_id("unit 10<>500"),
Ok(DetectModbusRust {
unit_id: Some(10..500),
..Default::default()
})
);
assert_eq!(parse_unit_id("unit ๖"), Err(()));
assert_eq!(parse_access("access write holdin"), Err(()));
assert_eq!(parse_access("unt 10"), Err(()));
assert_eq!(
parse_access("access write holding, address 100, value 500<>"),
Err(())
);
}
#[test]
fn test_match() {
let mut modbus = ModbusState::new();
assert_eq!(
modbus.parse(
std::ptr::null_mut(),
&[
0x12, 0x34, 0x00, 0x00, 0x00, 0x11, 0x0a, 0x17, 0x00, 0x03, 0x00, 0x06, 0x00, 0x0E, 0x00, 0x03, 0x06, 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC
],
Direction::ToServer
),
AppLayerResult::ok()
);
assert_eq!(modbus.transactions.len(), 1);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
function: Some(FunctionCode::RdWrMultRegs),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(15..15),
value: Some(u16::MIN..4660),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(15..15),
value: Some(4661..4661),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(16..16),
value: Some(20000..22136),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(16..16),
value: Some(22136..30000),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(15..15),
value: Some(4660..u16::MAX),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(16..16),
value: Some(u16::MIN..22137),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(16..16),
value: Some(u16::MIN..22137),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(17..17),
value: Some(39612..39612),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(17..17),
value: Some(30000..39613),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(15..15),
value: Some(4659..5000),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
access_type: Some(AccessType::WRITE | AccessType::HOLDING),
address: Some(17..17),
value: Some(39611..u16::MAX),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
unit_id: Some(12..12),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
unit_id: Some(5..9),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
unit_id: Some(11..15),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
unit_id: Some(11..u16::MAX),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
unit_id: Some(u16::MIN..9),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
unit_id: Some(10..10),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
unit_id: Some(5..15),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
unit_id: Some(9..u16::MAX),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
unit_id: Some(u16::MIN..11),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
function: Some(FunctionCode::RdFileRec),
unit_id: Some(10..10),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
function: Some(FunctionCode::RdFileRec),
unit_id: Some(11..11),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
function: Some(FunctionCode::RdWrMultRegs),
unit_id: Some(11..11),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
category: Some(CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED),
unit_id: Some(11..11),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
category: Some(Flags::from(CodeCategory::USER_DEFINED)),
unit_id: Some(10..10),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
function: Some(FunctionCode::RdWrMultRegs),
unit_id: Some(10..10),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
category: Some(CodeCategory::PUBLIC_ASSIGNED | CodeCategory::PUBLIC_UNASSIGNED),
unit_id: Some(10..10),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[0],
&DetectModbusRust {
category: Some(!CodeCategory::USER_DEFINED),
unit_id: Some(10..10),
..Default::default()
}
),
1
);
assert_eq!(
modbus.parse(
std::ptr::null_mut(),
&[
0x0A, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x08, 0x00, 0x04, 0x00, 0x00 ],
Direction::ToServer
),
AppLayerResult::ok()
);
assert_eq!(modbus.transactions.len(), 2);
assert_eq!(
SCModbusInspect(
&modbus.transactions[1],
&DetectModbusRust {
function: Some(FunctionCode::Diagnostic),
subfunction: Some(4),
..Default::default()
}
),
1
);
assert_eq!(
modbus.parse(std::ptr::null_mut(),
&[
0x00, 0x10, 0x00, 0x00, 0x00, 0x05, 0x00, 0x2B, 0x0F, 0x00, 0x00 ],
Direction::ToServer
),
AppLayerResult::ok()
);
assert_eq!(modbus.transactions.len(), 3);
assert_eq!(
SCModbusInspect(
&modbus.transactions[2],
&DetectModbusRust {
category: Some(Flags::from(CodeCategory::RESERVED)),
..Default::default()
}
),
1
);
assert_eq!(
modbus.parse(std::ptr::null_mut(),
&[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x02, 0x00, 0x12 ],
Direction::ToServer
),
AppLayerResult::ok()
);
assert_eq!(modbus.transactions.len(), 4);
assert_ne!(
SCModbusInspect(
&modbus.transactions[3],
&DetectModbusRust {
category: Some(!CodeCategory::PUBLIC_ASSIGNED),
..Default::default()
}
),
1
);
assert_eq!(
modbus.parse(std::ptr::null_mut(),
&[
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0a, 0x01, 0x78, 0x90, 0x00, 0x13 ],
Direction::ToServer
),
AppLayerResult::ok()
);
assert_eq!(modbus.transactions.len(), 5);
assert_eq!(
SCModbusInspect(
&modbus.transactions[4],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[4],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
address: Some(30870..30870),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[4],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
unit_id: Some(10..10),
address: Some(30863..30863),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[4],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
unit_id: Some(11..11),
address: Some(30870..30870),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[4],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
unit_id: Some(11..11),
address: Some(30863..30863),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[4],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::WRITE)),
unit_id: Some(10..10),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[4],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
unit_id: Some(10..10),
address: Some(30870..30870),
..Default::default()
}
),
1
);
assert_eq!(
modbus.parse(std::ptr::null_mut(),
&[
0x00, 0x0A, 0x00, 0x00, 0x00, 0x06, 0x00, 0x04, 0x00, 0x08, 0x00, 0x60 ],
Direction::ToServer
),
AppLayerResult::ok()
);
assert_eq!(modbus.transactions.len(), 6);
assert_eq!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(u16::MIN..9),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(5..9),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(104..u16::MAX),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(104..110),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(9..9),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(u16::MIN..10),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(5..10),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(103..u16::MAX),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(103..110),
..Default::default()
}
),
1
);
assert_eq!(
SCModbusInspect(
&modbus.transactions[5],
&DetectModbusRust {
access_type: Some(AccessType::READ | AccessType::INPUT),
address: Some(104..104),
..Default::default()
}
),
1
);
assert_eq!(
modbus.parse(std::ptr::null_mut(),
&[
0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x0a, 0x01, 0x01, 0x00, ],
Direction::ToClient
),
AppLayerResult::ok()
);
assert_eq!(modbus.transactions.len(), 7);
assert_eq!(
SCModbusInspect(
&modbus.transactions[6],
&DetectModbusRust {
function: Some(FunctionCode::RdCoils),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[6],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
address: Some(104..104),
..Default::default()
}
),
1
);
assert_eq!(
modbus.parse(std::ptr::null_mut(),
&[
0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x0a, 0x06, 0x00, 0x05, 0x00, 0x0b ],
Direction::ToClient
),
AppLayerResult::ok()
);
assert_eq!(modbus.transactions.len(), 8);
assert_eq!(
SCModbusInspect(
&modbus.transactions[7],
&DetectModbusRust {
function: Some(FunctionCode::WrSingleReg),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[7],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::WRITE)),
address: Some(10..10),
..Default::default()
}
),
1
);
assert_eq!(
modbus.parse(std::ptr::null_mut(),
&[
0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x0a, 0x08, 0x00, 0x0a, 0x00, 0x00 ],
Direction::ToClient
),
AppLayerResult::ok()
);
assert_eq!(modbus.transactions.len(), 9);
assert_eq!(
SCModbusInspect(
&modbus.transactions[8],
&DetectModbusRust {
function: Some(FunctionCode::Diagnostic),
..Default::default()
}
),
1
);
assert_ne!(
SCModbusInspect(
&modbus.transactions[8],
&DetectModbusRust {
access_type: Some(Flags::from(AccessType::READ)),
..Default::default()
}
),
1
);
}
}