/src/suricata7/rust/src/modbus/modbus.rs
Line | Count | Source |
1 | | /* Copyright (C) 2021 Open Information Security Foundation |
2 | | * |
3 | | * You can copy, redistribute or modify this Program under the terms of |
4 | | * the GNU General Public License version 2 as published by the Free |
5 | | * Software Foundation. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * version 2 along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
15 | | * 02110-1301, USA. |
16 | | */ |
17 | | use crate::applayer::{self, *}; |
18 | | use crate::core::{self, AppProto, ALPROTO_FAILED, ALPROTO_UNKNOWN, IPPROTO_TCP}; |
19 | | |
20 | | use std::ffi::CString; |
21 | | |
22 | | use sawp::error::Error as SawpError; |
23 | | use sawp::error::ErrorKind as SawpErrorKind; |
24 | | use sawp::parser::{Direction, Parse}; |
25 | | use sawp::probe::{Probe, Status}; |
26 | | use sawp_modbus::{self, AccessType, ErrorFlags, Flags, Message}; |
27 | | |
28 | | pub const REQUEST_FLOOD: usize = 500; // Default unreplied Modbus requests are considered a flood |
29 | | pub const MODBUS_PARSER: sawp_modbus::Modbus = sawp_modbus::Modbus { probe_strict: true }; |
30 | | |
31 | | static mut ALPROTO_MODBUS: AppProto = ALPROTO_UNKNOWN; |
32 | | |
33 | | #[derive(AppLayerEvent)] |
34 | | enum ModbusEvent { |
35 | | UnsolicitedResponse, |
36 | | InvalidFunctionCode, |
37 | | InvalidLength, |
38 | | InvalidValue, |
39 | | InvalidExceptionCode, |
40 | | ValueMismatch, |
41 | | Flooded, |
42 | | InvalidProtocolId, |
43 | | } |
44 | | pub struct ModbusTransaction { |
45 | | pub id: u64, |
46 | | |
47 | | pub request: Option<Message>, |
48 | | pub response: Option<Message>, |
49 | | |
50 | | pub tx_data: AppLayerTxData, |
51 | | } |
52 | | |
53 | | impl Transaction for ModbusTransaction { |
54 | 125M | fn id(&self) -> u64 { |
55 | 125M | self.id |
56 | 125M | } |
57 | | } |
58 | | |
59 | | impl ModbusTransaction { |
60 | 1.77M | pub fn new(id: u64) -> Self { |
61 | 1.77M | Self { |
62 | 1.77M | id, |
63 | 1.77M | request: None, |
64 | 1.77M | response: None, |
65 | 1.77M | tx_data: AppLayerTxData::new(), |
66 | 1.77M | } |
67 | 1.77M | } |
68 | | |
69 | 3.64M | fn set_event(&mut self, event: ModbusEvent) { |
70 | 3.64M | self.tx_data.set_event(event as u8); |
71 | 3.64M | } |
72 | | |
73 | 1.94M | fn set_events_from_flags(&mut self, flags: &Flags<ErrorFlags>) { |
74 | 1.94M | if flags.intersects(ErrorFlags::FUNC_CODE) { |
75 | 18.7k | self.set_event(ModbusEvent::InvalidFunctionCode); |
76 | 1.92M | } |
77 | 1.94M | if flags.intersects(ErrorFlags::DATA_VALUE) { |
78 | 22.7k | self.set_event(ModbusEvent::InvalidValue); |
79 | 1.92M | } |
80 | 1.94M | if flags.intersects(ErrorFlags::DATA_LENGTH) { |
81 | 1.83M | self.set_event(ModbusEvent::InvalidLength); |
82 | 1.83M | } |
83 | 1.94M | if flags.intersects(ErrorFlags::EXC_CODE) { |
84 | 21.5k | self.set_event(ModbusEvent::InvalidExceptionCode); |
85 | 1.92M | } |
86 | 1.94M | if flags.intersects(ErrorFlags::PROTO_ID) { |
87 | 302k | self.set_event(ModbusEvent::InvalidProtocolId); |
88 | 1.64M | } |
89 | 1.94M | } |
90 | | } |
91 | | |
92 | | #[derive(Default)] |
93 | | pub struct ModbusState { |
94 | | state_data: AppLayerStateData, |
95 | | pub transactions: Vec<ModbusTransaction>, |
96 | | tx_id: u64, |
97 | | givenup: bool, // Indicates flood |
98 | | } |
99 | | |
100 | | impl State<ModbusTransaction> for ModbusState { |
101 | 48.2M | fn get_transaction_count(&self) -> usize { |
102 | 48.2M | self.transactions.len() |
103 | 48.2M | } |
104 | | |
105 | 77.2M | fn get_transaction_by_index(&self, index: usize) -> Option<&ModbusTransaction> { |
106 | 77.2M | self.transactions.get(index) |
107 | 77.2M | } |
108 | | } |
109 | | |
110 | | impl ModbusState { |
111 | 7.64k | pub fn new() -> Self { |
112 | 7.64k | Default::default() |
113 | 7.64k | } |
114 | | |
115 | 18.9k | pub fn get_tx(&mut self, tx_id: u64) -> Option<&mut ModbusTransaction> { |
116 | 2.21M | self.transactions.iter_mut().find(|tx| tx.id == tx_id + 1) |
117 | 18.9k | } |
118 | | |
119 | | /// Searches the requests in order to find one matching the given response. Returns the matching |
120 | | /// transaction, if it exists |
121 | 1.61M | pub fn find_request_and_validate( |
122 | 1.61M | &mut self, resp: &mut Message, |
123 | 1.61M | ) -> Option<&mut ModbusTransaction> { |
124 | 208M | for tx in &mut self.transactions { |
125 | 207M | if let Some(req) = &tx.request { |
126 | 20.5M | if tx.response.is_none() && resp.matches(req) { |
127 | 204k | tx.tx_data.updated_tc = true; |
128 | 204k | tx.tx_data.updated_ts = true; |
129 | 204k | return Some(tx); |
130 | 20.3M | } |
131 | 186M | } |
132 | | } |
133 | 1.40M | None |
134 | 1.61M | } |
135 | | |
136 | | /// Searches the responses in order to find one matching the given request. Returns the matching |
137 | | /// transaction, if it exists |
138 | 365k | pub fn find_response_and_validate( |
139 | 365k | &mut self, req: &mut Message, |
140 | 365k | ) -> Option<&mut ModbusTransaction> { |
141 | 36.2M | for tx in &mut self.transactions { |
142 | 35.8M | if let Some(resp) = &tx.response { |
143 | 502k | if tx.request.is_none() && req.matches(resp) { |
144 | 2.95k | tx.tx_data.updated_tc = true; |
145 | 2.95k | tx.tx_data.updated_ts = true; |
146 | 2.95k | return Some(tx); |
147 | 499k | } |
148 | 35.3M | } |
149 | | } |
150 | 362k | None |
151 | 365k | } |
152 | | |
153 | 1.77M | pub fn new_tx(&mut self) -> Option<ModbusTransaction> { |
154 | | // Check flood limit |
155 | 1.77M | if self.givenup { |
156 | 92 | return None; |
157 | 1.77M | } |
158 | | |
159 | 1.77M | self.tx_id += 1; |
160 | 1.77M | let mut tx = ModbusTransaction::new(self.tx_id); |
161 | | |
162 | 1.77M | if REQUEST_FLOOD != 0 && self.transactions.len() >= REQUEST_FLOOD { |
163 | 474 | tx.set_event(ModbusEvent::Flooded); |
164 | 474 | self.givenup = true; |
165 | 1.77M | } |
166 | | |
167 | 1.77M | Some(tx) |
168 | 1.77M | } |
169 | | |
170 | 1.58M | pub fn free_tx(&mut self, tx_id: u64) { |
171 | 21.1M | if let Some(index) = self.transactions.iter().position(|tx| tx.id == tx_id + 1) { |
172 | 1.58M | self.transactions.remove(index); |
173 | | |
174 | | // Check flood limit |
175 | 1.58M | if self.givenup && REQUEST_FLOOD != 0 && self.transactions.len() < REQUEST_FLOOD { |
176 | 386 | self.givenup = false; |
177 | 1.58M | } |
178 | 0 | } |
179 | 1.58M | } |
180 | | |
181 | 414k | pub fn parse(&mut self, input: &[u8], direction: Direction) -> AppLayerResult { |
182 | 414k | let mut rest = input; |
183 | 2.39M | while !rest.is_empty() { |
184 | 2.21M | match MODBUS_PARSER.parse(rest, direction.clone()) { |
185 | 1.97M | Ok((inner_rest, Some(mut msg))) => { |
186 | 1.97M | match direction { |
187 | | Direction::ToServer | Direction::Unknown => { |
188 | 365k | match self.find_response_and_validate(&mut msg) { |
189 | 2.95k | Some(tx) => { |
190 | 2.95k | tx.set_events_from_flags(&msg.error_flags); |
191 | 2.95k | tx.tx_data.updated_tc = true; |
192 | 2.95k | tx.tx_data.updated_ts = true; |
193 | 2.95k | tx.request = Some(msg); |
194 | 2.95k | } |
195 | | None => { |
196 | 362k | let mut tx = match self.new_tx() { |
197 | 362k | Some(tx) => tx, |
198 | 66 | None => return AppLayerResult::err(), |
199 | | }; |
200 | 362k | tx.set_events_from_flags(&msg.error_flags); |
201 | 362k | tx.request = Some(msg); |
202 | 362k | self.transactions.push(tx); |
203 | | } |
204 | | } |
205 | | } |
206 | 1.61M | Direction::ToClient => match self.find_request_and_validate(&mut msg) { |
207 | 204k | Some(tx) => { |
208 | 204k | if msg |
209 | 204k | .access_type |
210 | 204k | .intersects(AccessType::READ | AccessType::WRITE) |
211 | 19.3k | && msg.error_flags.intersects( |
212 | 19.3k | ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE, |
213 | | ) |
214 | 17.5k | { |
215 | 17.5k | tx.set_event(ModbusEvent::ValueMismatch); |
216 | 186k | } else { |
217 | 186k | tx.set_events_from_flags(&msg.error_flags); |
218 | 186k | } |
219 | 204k | tx.tx_data.updated_tc = true; |
220 | 204k | tx.tx_data.updated_ts = true; |
221 | 204k | tx.response = Some(msg); |
222 | | } |
223 | | None => { |
224 | 1.40M | let mut tx = match self.new_tx() { |
225 | 1.40M | Some(tx) => tx, |
226 | 26 | None => return AppLayerResult::err(), |
227 | | }; |
228 | 1.40M | if msg |
229 | 1.40M | .access_type |
230 | 1.40M | .intersects(AccessType::READ | AccessType::WRITE) |
231 | 19.2k | && msg.error_flags.intersects( |
232 | 19.2k | ErrorFlags::DATA_LENGTH | ErrorFlags::DATA_VALUE, |
233 | | ) |
234 | 16.4k | { |
235 | 16.4k | tx.set_event(ModbusEvent::ValueMismatch); |
236 | 1.39M | } else { |
237 | 1.39M | tx.set_events_from_flags(&msg.error_flags); |
238 | 1.39M | } |
239 | 1.40M | tx.response = Some(msg); |
240 | 1.40M | tx.set_event(ModbusEvent::UnsolicitedResponse); |
241 | 1.40M | self.transactions.push(tx); |
242 | | } |
243 | | }, |
244 | | } |
245 | | |
246 | 1.97M | if inner_rest.len() >= rest.len() { |
247 | 0 | return AppLayerResult::err(); |
248 | 1.97M | } |
249 | 1.97M | rest = inner_rest; |
250 | | } |
251 | 0 | Ok((inner_rest, None)) => { |
252 | 0 | return AppLayerResult::incomplete( |
253 | 0 | (input.len() - inner_rest.len()) as u32, |
254 | 0 | inner_rest.len() as u32 + 1, |
255 | | ); |
256 | | } |
257 | | Err(SawpError { |
258 | 238k | kind: SawpErrorKind::Incomplete(sawp::error::Needed::Size(needed)), |
259 | | }) => { |
260 | 238k | return AppLayerResult::incomplete( |
261 | 238k | (input.len() - rest.len()) as u32, |
262 | 238k | (rest.len() + needed.get()) as u32, |
263 | | ); |
264 | | } |
265 | | Err(SawpError { |
266 | | kind: SawpErrorKind::Incomplete(sawp::error::Needed::Unknown), |
267 | | }) => { |
268 | 0 | return AppLayerResult::incomplete( |
269 | 0 | (input.len() - rest.len()) as u32, |
270 | 0 | rest.len() as u32 + 1, |
271 | | ); |
272 | | } |
273 | 0 | Err(_) => return AppLayerResult::err(), |
274 | | } |
275 | | } |
276 | 175k | AppLayerResult::ok() |
277 | 414k | } |
278 | | } |
279 | | |
280 | | /// Probe input to see if it looks like Modbus. |
281 | | #[no_mangle] |
282 | 54.2k | pub extern "C" fn rs_modbus_probe( |
283 | 54.2k | _flow: *const core::Flow, _direction: u8, input: *const u8, len: u32, _rdir: *mut u8, |
284 | 54.2k | ) -> AppProto { |
285 | 54.2k | if input.is_null() { |
286 | 14.8k | return ALPROTO_UNKNOWN; |
287 | 39.4k | } |
288 | 39.4k | let slice: &[u8] = unsafe { std::slice::from_raw_parts(input as *mut u8, len as usize) }; |
289 | 39.4k | match MODBUS_PARSER.probe(slice, Direction::Unknown) { |
290 | 26.9k | Status::Recognized => unsafe { ALPROTO_MODBUS }, |
291 | 9.98k | Status::Incomplete => ALPROTO_UNKNOWN, |
292 | 2.48k | Status::Unrecognized => unsafe { ALPROTO_FAILED }, |
293 | | } |
294 | 54.2k | } |
295 | | |
296 | | #[no_mangle] |
297 | 7.64k | pub extern "C" fn rs_modbus_state_new( |
298 | 7.64k | _orig_state: *mut std::os::raw::c_void, _orig_proto: AppProto, |
299 | 7.64k | ) -> *mut std::os::raw::c_void { |
300 | 7.64k | Box::into_raw(Box::new(ModbusState::new())) as *mut std::os::raw::c_void |
301 | 7.64k | } |
302 | | |
303 | | #[no_mangle] |
304 | 7.64k | pub extern "C" fn rs_modbus_state_free(state: *mut std::os::raw::c_void) { |
305 | 7.64k | let _state: Box<ModbusState> = unsafe { Box::from_raw(state as *mut ModbusState) }; |
306 | 7.64k | } |
307 | | |
308 | | #[no_mangle] |
309 | 1.58M | pub unsafe extern "C" fn rs_modbus_state_tx_free(state: *mut std::os::raw::c_void, tx_id: u64) { |
310 | 1.58M | let state = cast_pointer!(state, ModbusState); |
311 | 1.58M | state.free_tx(tx_id); |
312 | 1.58M | } |
313 | | |
314 | | #[no_mangle] |
315 | 198k | pub unsafe extern "C" fn rs_modbus_parse_request( |
316 | 198k | _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, |
317 | 198k | stream_slice: StreamSlice, |
318 | 198k | _data: *const std::os::raw::c_void, |
319 | 198k | ) -> AppLayerResult { |
320 | 198k | let buf = stream_slice.as_slice(); |
321 | 198k | if buf.is_empty() { |
322 | 1.46k | if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TS) > 0 { |
323 | 1.46k | return AppLayerResult::ok(); |
324 | | } else { |
325 | 0 | return AppLayerResult::err(); |
326 | | } |
327 | 196k | } |
328 | | |
329 | 196k | let state = cast_pointer!(state, ModbusState); |
330 | 196k | state.parse(buf, Direction::ToServer) |
331 | 198k | } |
332 | | |
333 | | #[no_mangle] |
334 | 218k | pub unsafe extern "C" fn rs_modbus_parse_response( |
335 | 218k | _flow: *const core::Flow, state: *mut std::os::raw::c_void, pstate: *mut std::os::raw::c_void, |
336 | 218k | stream_slice: StreamSlice, |
337 | 218k | _data: *const std::os::raw::c_void, |
338 | 218k | ) -> AppLayerResult { |
339 | 218k | let buf = stream_slice.as_slice(); |
340 | 218k | if buf.is_empty() { |
341 | 285 | if AppLayerParserStateIssetFlag(pstate, APP_LAYER_PARSER_EOF_TC) > 0 { |
342 | 285 | return AppLayerResult::ok(); |
343 | | } else { |
344 | 0 | return AppLayerResult::err(); |
345 | | } |
346 | 218k | } |
347 | | |
348 | 218k | let state = cast_pointer!(state, ModbusState); |
349 | 218k | state.parse(buf, Direction::ToClient) |
350 | 218k | } |
351 | | |
352 | | #[no_mangle] |
353 | 1.35M | pub unsafe extern "C" fn rs_modbus_state_get_tx_count(state: *mut std::os::raw::c_void) -> u64 { |
354 | 1.35M | let state = cast_pointer!(state, ModbusState); |
355 | 1.35M | state.tx_id |
356 | 1.35M | } |
357 | | |
358 | | #[no_mangle] |
359 | 18.9k | pub unsafe extern "C" fn rs_modbus_state_get_tx( |
360 | 18.9k | state: *mut std::os::raw::c_void, tx_id: u64, |
361 | 18.9k | ) -> *mut std::os::raw::c_void { |
362 | 18.9k | let state = cast_pointer!(state, ModbusState); |
363 | 18.9k | match state.get_tx(tx_id) { |
364 | 18.7k | Some(tx) => (tx as *mut ModbusTransaction) as *mut std::os::raw::c_void, |
365 | 212 | None => std::ptr::null_mut(), |
366 | | } |
367 | 18.9k | } |
368 | | |
369 | | #[no_mangle] |
370 | 49.9M | pub unsafe extern "C" fn rs_modbus_tx_get_alstate_progress( |
371 | 49.9M | tx: *mut std::os::raw::c_void, _direction: u8, |
372 | 49.9M | ) -> std::os::raw::c_int { |
373 | 49.9M | let tx = cast_pointer!(tx, ModbusTransaction); |
374 | 49.9M | tx.response.is_some() as std::os::raw::c_int |
375 | 49.9M | } |
376 | | |
377 | | #[no_mangle] |
378 | 48.2M | pub unsafe extern "C" fn rs_modbus_state_get_tx_data( |
379 | 48.2M | tx: *mut std::os::raw::c_void, |
380 | 48.2M | ) -> *mut AppLayerTxData { |
381 | 48.2M | let tx = cast_pointer!(tx, ModbusTransaction); |
382 | 48.2M | &mut tx.tx_data |
383 | 48.2M | } |
384 | | |
385 | | export_state_data_get!(rs_modbus_get_state_data, ModbusState); |
386 | | |
387 | | #[no_mangle] |
388 | 34 | pub unsafe extern "C" fn rs_modbus_register_parser() { |
389 | 34 | let default_port = std::ffi::CString::new("[502]").unwrap(); |
390 | 34 | let parser = RustParser { |
391 | 34 | name: b"modbus\0".as_ptr() as *const std::os::raw::c_char, |
392 | 34 | default_port: default_port.as_ptr(), |
393 | 34 | ipproto: IPPROTO_TCP, |
394 | 34 | probe_ts: Some(rs_modbus_probe), |
395 | 34 | probe_tc: Some(rs_modbus_probe), |
396 | 34 | min_depth: 0, |
397 | 34 | max_depth: 16, |
398 | 34 | state_new: rs_modbus_state_new, |
399 | 34 | state_free: rs_modbus_state_free, |
400 | 34 | tx_free: rs_modbus_state_tx_free, |
401 | 34 | parse_ts: rs_modbus_parse_request, |
402 | 34 | parse_tc: rs_modbus_parse_response, |
403 | 34 | get_tx_count: rs_modbus_state_get_tx_count, |
404 | 34 | get_tx: rs_modbus_state_get_tx, |
405 | 34 | tx_comp_st_ts: 1, |
406 | 34 | tx_comp_st_tc: 1, |
407 | 34 | tx_get_progress: rs_modbus_tx_get_alstate_progress, |
408 | 34 | get_eventinfo: Some(ModbusEvent::get_event_info), |
409 | 34 | get_eventinfo_byid: Some(ModbusEvent::get_event_info_by_id), |
410 | 34 | localstorage_new: None, |
411 | 34 | localstorage_free: None, |
412 | 34 | get_tx_files: None, |
413 | 34 | get_tx_iterator: Some(applayer::state_get_tx_iterator::<ModbusState, ModbusTransaction>), |
414 | 34 | get_tx_data: rs_modbus_state_get_tx_data, |
415 | 34 | get_state_data: rs_modbus_get_state_data, |
416 | 34 | apply_tx_config: None, |
417 | 34 | flags: 0, |
418 | 34 | truncate: None, |
419 | 34 | get_frame_id_by_name: None, |
420 | 34 | get_frame_name_by_id: None, |
421 | 34 | }; |
422 | | |
423 | 34 | let ip_proto_str = CString::new("tcp").unwrap(); |
424 | 34 | if AppLayerProtoDetectConfProtoDetectionEnabledDefault(ip_proto_str.as_ptr(), parser.name, false) != 0 { |
425 | 34 | let alproto = AppLayerRegisterProtocolDetection(&parser, 1); |
426 | 34 | ALPROTO_MODBUS = alproto; |
427 | 34 | if AppLayerParserConfParserEnabled(ip_proto_str.as_ptr(), parser.name) != 0 { |
428 | 34 | let _ = AppLayerRegisterParser(&parser, alproto); |
429 | 34 | } |
430 | 0 | } |
431 | 34 | } |
432 | | |
433 | | // This struct and accessor functions are used for app-layer-modbus.c tests. |
434 | | pub mod test { |
435 | | use super::ModbusState; |
436 | | use sawp_modbus::{Data, Message, Read, Write}; |
437 | | use std::ffi::c_void; |
438 | | #[repr(C)] |
439 | | pub struct ModbusMessage(*const c_void); |
440 | | |
441 | | #[no_mangle] |
442 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_function(msg: *const ModbusMessage) -> u8 { |
443 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
444 | 0 | let msg = msg.as_ref().unwrap(); |
445 | 0 | msg.function.raw |
446 | 0 | } |
447 | | |
448 | | #[no_mangle] |
449 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_subfunction(msg: *const ModbusMessage) -> u16 { |
450 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
451 | 0 | let msg = msg.as_ref().unwrap(); |
452 | 0 | if let Data::Diagnostic { func, data: _ } = &msg.data { |
453 | 0 | func.raw |
454 | | } else { |
455 | 0 | panic!("wrong modbus message data type"); |
456 | | } |
457 | 0 | } |
458 | | |
459 | | #[no_mangle] |
460 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_read_request_address( |
461 | 0 | msg: *const ModbusMessage, |
462 | 0 | ) -> u16 { |
463 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
464 | 0 | let msg = msg.as_ref().unwrap(); |
465 | | if let Data::Read(Read::Request { |
466 | 0 | address, |
467 | | quantity: _, |
468 | 0 | }) = &msg.data |
469 | | { |
470 | 0 | *address |
471 | | } else { |
472 | 0 | panic!("wrong modbus message data type"); |
473 | | } |
474 | 0 | } |
475 | | |
476 | | #[no_mangle] |
477 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_read_request_quantity( |
478 | 0 | msg: *const ModbusMessage, |
479 | 0 | ) -> u16 { |
480 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
481 | 0 | let msg = msg.as_ref().unwrap(); |
482 | | if let Data::Read(Read::Request { |
483 | | address: _, |
484 | 0 | quantity, |
485 | 0 | }) = &msg.data |
486 | | { |
487 | 0 | *quantity |
488 | | } else { |
489 | 0 | panic!("wrong modbus message data type"); |
490 | | } |
491 | 0 | } |
492 | | |
493 | | #[no_mangle] |
494 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_address( |
495 | 0 | msg: *const ModbusMessage, |
496 | 0 | ) -> u16 { |
497 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
498 | 0 | let msg = msg.as_ref().unwrap(); |
499 | | if let Data::ReadWrite { |
500 | | read: |
501 | | Read::Request { |
502 | 0 | address, |
503 | | quantity: _, |
504 | | }, |
505 | | write: _, |
506 | 0 | } = &msg.data |
507 | | { |
508 | 0 | *address |
509 | | } else { |
510 | 0 | panic!("wrong modbus message data type"); |
511 | | } |
512 | 0 | } |
513 | | |
514 | | #[no_mangle] |
515 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_read_quantity( |
516 | 0 | msg: *const ModbusMessage, |
517 | 0 | ) -> u16 { |
518 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
519 | 0 | let msg = msg.as_ref().unwrap(); |
520 | | if let Data::ReadWrite { |
521 | | read: |
522 | | Read::Request { |
523 | | address: _, |
524 | 0 | quantity, |
525 | | }, |
526 | | write: _, |
527 | 0 | } = &msg.data |
528 | | { |
529 | 0 | *quantity |
530 | | } else { |
531 | 0 | panic!("wrong modbus message data type"); |
532 | | } |
533 | 0 | } |
534 | | |
535 | | #[no_mangle] |
536 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_address( |
537 | 0 | msg: *const ModbusMessage, |
538 | 0 | ) -> u16 { |
539 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
540 | 0 | let msg = msg.as_ref().unwrap(); |
541 | | if let Data::ReadWrite { |
542 | | read: _, |
543 | | write: |
544 | | Write::MultReq { |
545 | 0 | address, |
546 | | quantity: _, |
547 | | data: _, |
548 | | }, |
549 | 0 | } = &msg.data |
550 | | { |
551 | 0 | *address |
552 | | } else { |
553 | 0 | panic!("wrong modbus message data type"); |
554 | | } |
555 | 0 | } |
556 | | |
557 | | #[no_mangle] |
558 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_quantity( |
559 | 0 | msg: *const ModbusMessage, |
560 | 0 | ) -> u16 { |
561 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
562 | 0 | let msg = msg.as_ref().unwrap(); |
563 | | if let Data::ReadWrite { |
564 | | read: _, |
565 | | write: |
566 | | Write::MultReq { |
567 | | address: _, |
568 | 0 | quantity, |
569 | | data: _, |
570 | | }, |
571 | 0 | } = &msg.data |
572 | | { |
573 | 0 | *quantity |
574 | | } else { |
575 | 0 | panic!("wrong modbus message data type"); |
576 | | } |
577 | 0 | } |
578 | | |
579 | | #[no_mangle] |
580 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_rw_multreq_write_data( |
581 | 0 | msg: *const ModbusMessage, data_len: *mut usize, |
582 | 0 | ) -> *const u8 { |
583 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
584 | 0 | let msg = msg.as_ref().unwrap(); |
585 | | if let Data::ReadWrite { |
586 | | read: _, |
587 | | write: |
588 | | Write::MultReq { |
589 | | address: _, |
590 | | quantity: _, |
591 | 0 | data, |
592 | | }, |
593 | 0 | } = &msg.data |
594 | | { |
595 | 0 | *data_len = data.len(); |
596 | 0 | data.as_slice().as_ptr() |
597 | | } else { |
598 | 0 | panic!("wrong modbus message data type"); |
599 | | } |
600 | 0 | } |
601 | | |
602 | | #[no_mangle] |
603 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_address( |
604 | 0 | msg: *const ModbusMessage, |
605 | 0 | ) -> u16 { |
606 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
607 | 0 | let msg = msg.as_ref().unwrap(); |
608 | | if let Data::Write(Write::MultReq { |
609 | 0 | address, |
610 | | quantity: _, |
611 | | data: _, |
612 | 0 | }) = &msg.data |
613 | | { |
614 | 0 | *address |
615 | | } else { |
616 | 0 | panic!("wrong modbus message data type"); |
617 | | } |
618 | 0 | } |
619 | | |
620 | | #[no_mangle] |
621 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_quantity( |
622 | 0 | msg: *const ModbusMessage, |
623 | 0 | ) -> u16 { |
624 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
625 | 0 | let msg = msg.as_ref().unwrap(); |
626 | | if let Data::Write(Write::MultReq { |
627 | | address: _, |
628 | 0 | quantity, |
629 | | data: _, |
630 | 0 | }) = &msg.data |
631 | | { |
632 | 0 | *quantity |
633 | | } else { |
634 | 0 | panic!("wrong modbus message data type"); |
635 | | } |
636 | 0 | } |
637 | | |
638 | | #[no_mangle] |
639 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_write_multreq_data( |
640 | 0 | msg: *const ModbusMessage, data_len: *mut usize, |
641 | 0 | ) -> *const u8 { |
642 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
643 | 0 | let msg = msg.as_ref().unwrap(); |
644 | | if let Data::Write(Write::MultReq { |
645 | | address: _, |
646 | | quantity: _, |
647 | 0 | data, |
648 | 0 | }) = &msg.data |
649 | | { |
650 | 0 | *data_len = data.len(); |
651 | 0 | data.as_slice().as_ptr() |
652 | | } else { |
653 | 0 | panic!("wrong modbus message data type"); |
654 | | } |
655 | 0 | } |
656 | | |
657 | | #[no_mangle] |
658 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_and_mask(msg: *const ModbusMessage) -> u16 { |
659 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
660 | 0 | let msg = msg.as_ref().unwrap(); |
661 | | if let Data::Write(Write::Mask { |
662 | | address: _, |
663 | 0 | and_mask, |
664 | | or_mask: _, |
665 | 0 | }) = &msg.data |
666 | | { |
667 | 0 | *and_mask |
668 | | } else { |
669 | 0 | panic!("wrong modbus message data type"); |
670 | | } |
671 | 0 | } |
672 | | |
673 | | #[no_mangle] |
674 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_or_mask(msg: *const ModbusMessage) -> u16 { |
675 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
676 | 0 | let msg = msg.as_ref().unwrap(); |
677 | | if let Data::Write(Write::Mask { |
678 | | address: _, |
679 | | and_mask: _, |
680 | 0 | or_mask, |
681 | 0 | }) = &msg.data |
682 | | { |
683 | 0 | *or_mask |
684 | | } else { |
685 | 0 | panic!("wrong modbus message data type"); |
686 | | } |
687 | 0 | } |
688 | | |
689 | | #[no_mangle] |
690 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_write_address(msg: *const ModbusMessage) -> u16 { |
691 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
692 | 0 | let msg = msg.as_ref().unwrap(); |
693 | 0 | if let Data::Write(Write::Other { address, data: _ }) = &msg.data { |
694 | 0 | *address |
695 | | } else { |
696 | 0 | panic!("wrong modbus message data type"); |
697 | | } |
698 | 0 | } |
699 | | |
700 | | #[no_mangle] |
701 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_write_data(msg: *const ModbusMessage) -> u16 { |
702 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
703 | 0 | let msg = msg.as_ref().unwrap(); |
704 | 0 | if let Data::Write(Write::Other { address: _, data }) = &msg.data { |
705 | 0 | *data |
706 | | } else { |
707 | 0 | panic!("wrong modbus message data type"); |
708 | | } |
709 | 0 | } |
710 | | |
711 | | #[no_mangle] |
712 | 0 | pub unsafe extern "C" fn rs_modbus_message_get_bytevec_data( |
713 | 0 | msg: *const ModbusMessage, data_len: *mut usize, |
714 | 0 | ) -> *const u8 { |
715 | 0 | let msg = msg.as_ref().unwrap().0 as *const Message; |
716 | 0 | let msg = msg.as_ref().unwrap(); |
717 | 0 | if let Data::ByteVec(data) = &msg.data { |
718 | 0 | *data_len = data.len(); |
719 | 0 | data.as_slice().as_ptr() |
720 | | } else { |
721 | 0 | panic!("wrong modbus message data type"); |
722 | | } |
723 | 0 | } |
724 | | |
725 | | #[no_mangle] |
726 | 0 | pub unsafe extern "C" fn rs_modbus_state_get_tx_request( |
727 | 0 | state: *mut std::os::raw::c_void, tx_id: u64, |
728 | 0 | ) -> ModbusMessage { |
729 | 0 | let state = cast_pointer!(state, ModbusState); |
730 | 0 | if let Some(tx) = state.get_tx(tx_id) { |
731 | 0 | if let Some(request) = &tx.request { |
732 | 0 | ModbusMessage((request as *const Message) as *const c_void) |
733 | | } else { |
734 | 0 | ModbusMessage(std::ptr::null()) |
735 | | } |
736 | | } else { |
737 | 0 | ModbusMessage(std::ptr::null()) |
738 | | } |
739 | 0 | } |
740 | | |
741 | | #[no_mangle] |
742 | 0 | pub unsafe extern "C" fn rs_modbus_state_get_tx_response( |
743 | 0 | state: *mut std::os::raw::c_void, tx_id: u64, |
744 | 0 | ) -> ModbusMessage { |
745 | 0 | let state = cast_pointer!(state, ModbusState); |
746 | 0 | if let Some(tx) = state.get_tx(tx_id) { |
747 | 0 | if let Some(response) = &tx.response { |
748 | 0 | ModbusMessage((response as *const Message) as *const c_void) |
749 | | } else { |
750 | 0 | ModbusMessage(std::ptr::null()) |
751 | | } |
752 | | } else { |
753 | 0 | ModbusMessage(std::ptr::null()) |
754 | | } |
755 | 0 | } |
756 | | } |
757 | | |
758 | | #[cfg(test)] |
759 | | mod tests { |
760 | | use super::*; |
761 | | use sawp_modbus::{ |
762 | | Data, Diagnostic, DiagnosticSubfunction, Exception, ExceptionCode, FunctionCode, Read, |
763 | | Write, |
764 | | }; |
765 | | |
766 | | const INVALID_FUNC_CODE: &[u8] = &[ |
767 | | 0x00, 0x00, // Transaction ID |
768 | | 0x00, 0x00, // Protocol ID |
769 | | 0x00, 0x02, // Length |
770 | | 0x00, // Unit ID |
771 | | 0x00, // Function code |
772 | | ]; |
773 | | |
774 | | const RD_COILS_REQ: &[u8] = &[ |
775 | | 0x00, 0x00, // Transaction ID |
776 | | 0x00, 0x00, // Protocol ID |
777 | | 0x00, 0x06, // Length |
778 | | 0x00, // Unit ID |
779 | | 0x01, // Function code |
780 | | 0x78, 0x90, // Starting Address |
781 | | 0x00, 0x13, // Quantity of coils |
782 | | ]; |
783 | | |
784 | | const RD_COILS_RESP: &[u8] = &[ |
785 | | 0x00, 0x00, // Transaction ID |
786 | | 0x00, 0x00, // Protocol ID |
787 | | 0x00, 0x06, // Length |
788 | | 0x00, // Unit ID |
789 | | 0x01, // Function code |
790 | | 0x03, // Byte count |
791 | | 0xCD, 0x6B, 0x05, // Coil Status |
792 | | ]; |
793 | | |
794 | | const RD_COILS_ERR_RESP: &[u8] = &[ |
795 | | 0x00, 0x00, // Transaction ID |
796 | | 0x00, 0x00, // Protocol ID |
797 | | 0x00, 0x03, // Length |
798 | | 0x00, // Unit ID |
799 | | 0x81, // Function code |
800 | | 0xFF, // Exception code |
801 | | ]; |
802 | | |
803 | | const WR_SINGLE_REG_REQ: &[u8] = &[ |
804 | | 0x00, 0x0A, // Transaction ID |
805 | | 0x00, 0x00, // Protocol ID |
806 | | 0x00, 0x06, // Length |
807 | | 0x00, // Unit ID |
808 | | 0x06, // Function code |
809 | | 0x00, 0x01, // Register Address |
810 | | 0x00, 0x03, // Register Value |
811 | | ]; |
812 | | |
813 | | const INVALID_WR_SINGLE_REG_REQ: &[u8] = &[ |
814 | | 0x00, 0x0A, // Transaction ID |
815 | | 0x00, 0x00, // Protocol ID |
816 | | 0x00, 0x04, // Length |
817 | | 0x00, // Unit ID |
818 | | 0x06, // Function code |
819 | | 0x00, 0x01, // Register Address |
820 | | ]; |
821 | | |
822 | | const WR_SINGLE_REG_RESP: &[u8] = &[ |
823 | | 0x00, 0x0A, // Transaction ID |
824 | | 0x00, 0x00, // Protocol ID |
825 | | 0x00, 0x06, // Length |
826 | | 0x00, // Unit ID |
827 | | 0x06, // Function code |
828 | | 0x00, 0x01, // Register Address |
829 | | 0x00, 0x03, // Register Value |
830 | | ]; |
831 | | |
832 | | const WR_MULT_REG_REQ: &[u8] = &[ |
833 | | 0x00, 0x0A, // Transaction ID |
834 | | 0x00, 0x00, // Protocol ID |
835 | | 0x00, 0x0B, // Length |
836 | | 0x00, // Unit ID |
837 | | 0x10, // Function code |
838 | | 0x00, 0x01, // Starting Address |
839 | | 0x00, 0x02, // Quantity of Registers |
840 | | 0x04, // Byte count |
841 | | 0x00, 0x0A, // Registers Value |
842 | | 0x01, 0x02, |
843 | | ]; |
844 | | |
845 | | const INVALID_PDU_WR_MULT_REG_REQ: &[u8] = &[ |
846 | | 0x00, 0x0A, // Transaction ID |
847 | | 0x00, 0x00, // Protocol ID |
848 | | 0x00, 0x02, // Length |
849 | | 0x00, // Unit ID |
850 | | 0x10, // Function code |
851 | | ]; |
852 | | |
853 | | const WR_MULT_REG_RESP: &[u8] = &[ |
854 | | 0x00, 0x0A, // Transaction ID |
855 | | 0x00, 0x00, // Protocol ID |
856 | | 0x00, 0x06, // Length |
857 | | 0x00, // Unit ID |
858 | | 0x10, // Function code |
859 | | 0x00, 0x01, // Starting Address |
860 | | 0x00, 0x02, // Quantity of Registers |
861 | | ]; |
862 | | |
863 | | const MASK_WR_REG_REQ: &[u8] = &[ |
864 | | 0x00, 0x0A, // Transaction ID |
865 | | 0x00, 0x00, // Protocol ID |
866 | | 0x00, 0x08, // Length |
867 | | 0x00, // Unit ID |
868 | | 0x16, // Function code |
869 | | 0x00, 0x04, // Reference Address |
870 | | 0x00, 0xF2, // And_Mask |
871 | | 0x00, 0x25, // Or_Mask |
872 | | ]; |
873 | | |
874 | | const INVALID_MASK_WR_REG_REQ: &[u8] = &[ |
875 | | 0x00, 0x0A, // Transaction ID |
876 | | 0x00, 0x00, // Protocol ID |
877 | | 0x00, 0x06, // Length |
878 | | 0x00, // Unit ID |
879 | | 0x16, // Function code |
880 | | 0x00, 0x04, // Reference Address |
881 | | 0x00, 0xF2, // And_Mask |
882 | | ]; |
883 | | |
884 | | const MASK_WR_REG_RESP: &[u8] = &[ |
885 | | 0x00, 0x0A, // Transaction ID |
886 | | 0x00, 0x00, // Protocol ID |
887 | | 0x00, 0x08, // Length |
888 | | 0x00, // Unit ID |
889 | | 0x16, // Function code |
890 | | 0x00, 0x04, // Reference Address |
891 | | 0x00, 0xF2, // And_Mask |
892 | | 0x00, 0x25, // Or_Mask |
893 | | ]; |
894 | | |
895 | | const RD_WR_MULT_REG_REQ: &[u8] = &[ |
896 | | 0x12, 0x34, // Transaction ID |
897 | | 0x00, 0x00, // Protocol ID |
898 | | 0x00, 0x11, // Length |
899 | | 0x00, // Unit ID |
900 | | 0x17, // Function code |
901 | | 0x00, 0x03, // Read Starting Address |
902 | | 0x00, 0x06, // Quantity to Read |
903 | | 0x00, 0x0E, // Write Starting Address |
904 | | 0x00, 0x03, // Quantity to Write |
905 | | 0x06, // Write Byte count |
906 | | 0x12, 0x34, // Write Registers Value |
907 | | 0x56, 0x78, 0x9A, 0xBC, |
908 | | ]; |
909 | | |
910 | | // Mismatch value in Byte count 0x0B instead of 0x0C |
911 | | const RD_WR_MULT_REG_RESP: &[u8] = &[ |
912 | | 0x12, 0x34, // Transaction ID |
913 | | 0x00, 0x00, // Protocol ID |
914 | | 0x00, 0x0E, // Length |
915 | | 0x00, // Unit ID |
916 | | 0x17, // Function code |
917 | | 0x0B, // Byte count |
918 | | 0x00, 0xFE, // Read Registers Value |
919 | | 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00, |
920 | | ]; |
921 | | |
922 | | const FORCE_LISTEN_ONLY_MODE: &[u8] = &[ |
923 | | 0x0A, 0x00, // Transaction ID |
924 | | 0x00, 0x00, // Protocol ID |
925 | | 0x00, 0x06, // Length |
926 | | 0x00, // Unit ID |
927 | | 0x08, // Function code |
928 | | 0x00, 0x04, // Sub-function code |
929 | | 0x00, 0x00, // Data |
930 | | ]; |
931 | | |
932 | | const INVALID_PROTO_REQ: &[u8] = &[ |
933 | | 0x00, 0x00, // Transaction ID |
934 | | 0x00, 0x01, // Protocol ID |
935 | | 0x00, 0x06, // Length |
936 | | 0x00, // Unit ID |
937 | | 0x01, // Function code |
938 | | 0x78, 0x90, // Starting Address |
939 | | 0x00, 0x13, // Quantity of coils |
940 | | ]; |
941 | | |
942 | | const INVALID_LEN_WR_MULT_REG_REQ: &[u8] = &[ |
943 | | 0x00, 0x0A, // Transaction ID |
944 | | 0x00, 0x00, // Protocol ID |
945 | | 0x00, 0x09, // Length |
946 | | 0x00, // Unit ID |
947 | | 0x10, // Function code |
948 | | 0x00, 0x01, // Starting Address |
949 | | 0x00, 0x02, // Quantity of Registers |
950 | | 0x04, // Byte count |
951 | | 0x00, 0x0A, // Registers Value |
952 | | 0x01, 0x02, |
953 | | ]; |
954 | | |
955 | | const EXCEEDED_LEN_WR_MULT_REG_REQ: &[u8] = &[ |
956 | | 0x00, 0x0A, // Transaction ID |
957 | | 0x00, 0x00, // Protocol ID |
958 | | 0xff, 0xfa, // Length |
959 | | 0x00, // Unit ID |
960 | | 0x10, // Function code |
961 | | 0x00, 0x01, // Starting Address |
962 | | 0x7f, 0xf9, // Quantity of Registers |
963 | | 0xff, // Byte count |
964 | | ]; |
965 | | |
966 | | #[test] |
967 | | fn read_coils() { |
968 | | let mut state = ModbusState::new(); |
969 | | assert_eq!( |
970 | | AppLayerResult::ok(), |
971 | | state.parse(RD_COILS_REQ, Direction::ToServer) |
972 | | ); |
973 | | assert_eq!(state.transactions.len(), 1); |
974 | | |
975 | | let tx = &state.transactions[0]; |
976 | | let msg = tx.request.as_ref().unwrap(); |
977 | | assert_eq!(msg.function.code, FunctionCode::RdCoils); |
978 | | assert_eq!( |
979 | | msg.data, |
980 | | Data::Read(Read::Request { |
981 | | address: 0x7890, |
982 | | quantity: 0x0013 |
983 | | }) |
984 | | ); |
985 | | |
986 | | assert_eq!( |
987 | | AppLayerResult::ok(), |
988 | | state.parse(RD_COILS_RESP, Direction::ToClient) |
989 | | ); |
990 | | assert_eq!(state.transactions.len(), 1); |
991 | | |
992 | | let tx = &state.transactions[0]; |
993 | | let msg = tx.response.as_ref().unwrap(); |
994 | | assert_eq!(msg.function.code, FunctionCode::RdCoils); |
995 | | assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05]))); |
996 | | } |
997 | | |
998 | | #[test] |
999 | | fn write_multiple_registers() { |
1000 | | let mut state = ModbusState::new(); |
1001 | | assert_eq!( |
1002 | | AppLayerResult::ok(), |
1003 | | state.parse(WR_MULT_REG_REQ, Direction::ToServer) |
1004 | | ); |
1005 | | assert_eq!(state.transactions.len(), 1); |
1006 | | |
1007 | | let tx = &state.transactions[0]; |
1008 | | let msg = tx.request.as_ref().unwrap(); |
1009 | | assert_eq!(msg.function.code, FunctionCode::WrMultRegs); |
1010 | | assert_eq!( |
1011 | | msg.data, |
1012 | | Data::Write(Write::MultReq { |
1013 | | address: 0x0001, |
1014 | | quantity: 0x0002, |
1015 | | data: vec![0x00, 0x0a, 0x01, 0x02], |
1016 | | }) |
1017 | | ); |
1018 | | |
1019 | | assert_eq!( |
1020 | | AppLayerResult::ok(), |
1021 | | state.parse(WR_MULT_REG_RESP, Direction::ToClient) |
1022 | | ); |
1023 | | assert_eq!(state.transactions.len(), 1); |
1024 | | |
1025 | | let tx = &state.transactions[0]; |
1026 | | let msg = tx.response.as_ref().unwrap(); |
1027 | | assert_eq!(msg.function.code, FunctionCode::WrMultRegs); |
1028 | | assert_eq!( |
1029 | | msg.data, |
1030 | | Data::Write(Write::Other { |
1031 | | address: 0x0001, |
1032 | | data: 0x0002 |
1033 | | }) |
1034 | | ); |
1035 | | } |
1036 | | |
1037 | | #[test] |
1038 | | fn read_write_multiple_registers() { |
1039 | | let mut state = ModbusState::new(); |
1040 | | assert_eq!( |
1041 | | AppLayerResult::ok(), |
1042 | | state.parse(RD_WR_MULT_REG_REQ, Direction::ToServer) |
1043 | | ); |
1044 | | assert_eq!(state.transactions.len(), 1); |
1045 | | |
1046 | | let tx = &state.transactions[0]; |
1047 | | let msg = tx.request.as_ref().unwrap(); |
1048 | | assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs); |
1049 | | assert_eq!( |
1050 | | msg.data, |
1051 | | Data::ReadWrite { |
1052 | | read: Read::Request { |
1053 | | address: 0x0003, |
1054 | | quantity: 0x0006, |
1055 | | }, |
1056 | | write: Write::MultReq { |
1057 | | address: 0x000e, |
1058 | | quantity: 0x0003, |
1059 | | data: vec![0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc] |
1060 | | } |
1061 | | } |
1062 | | ); |
1063 | | |
1064 | | assert_eq!( |
1065 | | AppLayerResult::ok(), |
1066 | | state.parse(RD_WR_MULT_REG_RESP, Direction::ToClient) |
1067 | | ); |
1068 | | assert_eq!(state.transactions.len(), 1); |
1069 | | |
1070 | | let tx = &state.transactions[0]; |
1071 | | let msg = tx.response.as_ref().unwrap(); |
1072 | | assert_eq!(msg.function.code, FunctionCode::RdWrMultRegs); |
1073 | | assert_eq!( |
1074 | | msg.data, |
1075 | | Data::Read(Read::Response(vec![ |
1076 | | 0x00, 0xFE, 0x0A, 0xCD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0D, 0x00, |
1077 | | ])) |
1078 | | ); |
1079 | | } |
1080 | | |
1081 | | #[test] |
1082 | | fn force_listen_only_mode() { |
1083 | | let mut state = ModbusState::new(); |
1084 | | assert_eq!( |
1085 | | AppLayerResult::ok(), |
1086 | | state.parse(FORCE_LISTEN_ONLY_MODE, Direction::ToServer) |
1087 | | ); |
1088 | | assert_eq!(state.transactions.len(), 1); |
1089 | | |
1090 | | let tx = &state.transactions[0]; |
1091 | | let msg = tx.request.as_ref().unwrap(); |
1092 | | assert_eq!(msg.function.code, FunctionCode::Diagnostic); |
1093 | | assert_eq!( |
1094 | | msg.data, |
1095 | | Data::Diagnostic { |
1096 | | func: Diagnostic { |
1097 | | raw: 4, |
1098 | | code: DiagnosticSubfunction::ForceListenOnlyMode |
1099 | | }, |
1100 | | data: vec![0x00, 0x00] |
1101 | | } |
1102 | | ); |
1103 | | } |
1104 | | |
1105 | | #[test] |
1106 | | fn invalid_protocol_version() { |
1107 | | let mut state = ModbusState::new(); |
1108 | | assert_eq!( |
1109 | | AppLayerResult::ok(), |
1110 | | state.parse(INVALID_PROTO_REQ, Direction::ToServer) |
1111 | | ); |
1112 | | |
1113 | | assert_eq!(state.transactions.len(), 1); |
1114 | | let tx = &state.transactions[0]; |
1115 | | let msg = tx.request.as_ref().unwrap(); |
1116 | | assert_eq!(msg.error_flags, ErrorFlags::PROTO_ID); |
1117 | | } |
1118 | | |
1119 | | #[test] |
1120 | | fn unsolicited_response() { |
1121 | | let mut state = ModbusState::new(); |
1122 | | assert_eq!( |
1123 | | AppLayerResult::ok(), |
1124 | | state.parse(RD_COILS_RESP, Direction::ToClient) |
1125 | | ); |
1126 | | assert_eq!(state.transactions.len(), 1); |
1127 | | |
1128 | | let tx = &state.transactions[0]; |
1129 | | let msg = tx.response.as_ref().unwrap(); |
1130 | | assert_eq!(msg.function.code, FunctionCode::RdCoils); |
1131 | | assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05]))); |
1132 | | } |
1133 | | |
1134 | | #[test] |
1135 | | fn invalid_length_request() { |
1136 | | let mut state = ModbusState::new(); |
1137 | | assert_eq!( |
1138 | | AppLayerResult::incomplete(15, 4), |
1139 | | state.parse(INVALID_LEN_WR_MULT_REG_REQ, Direction::ToServer) |
1140 | | ); |
1141 | | assert_eq!(state.transactions.len(), 1); |
1142 | | |
1143 | | let tx = &state.transactions[0]; |
1144 | | let msg = tx.request.as_ref().unwrap(); |
1145 | | assert_eq!(msg.function.code, FunctionCode::WrMultRegs); |
1146 | | assert_eq!( |
1147 | | msg.data, |
1148 | | Data::Write(Write::MultReq { |
1149 | | address: 0x0001, |
1150 | | quantity: 0x0002, |
1151 | | data: vec![0x00, 0x0a] |
1152 | | }) |
1153 | | ); |
1154 | | assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); |
1155 | | } |
1156 | | |
1157 | | #[test] |
1158 | | fn exception_code_invalid() { |
1159 | | let mut state = ModbusState::new(); |
1160 | | assert_eq!( |
1161 | | AppLayerResult::ok(), |
1162 | | state.parse(RD_COILS_REQ, Direction::ToServer) |
1163 | | ); |
1164 | | assert_eq!(state.transactions.len(), 1); |
1165 | | |
1166 | | let tx = &state.transactions[0]; |
1167 | | let msg = tx.request.as_ref().unwrap(); |
1168 | | assert_eq!(msg.function.code, FunctionCode::RdCoils); |
1169 | | assert_eq!( |
1170 | | msg.data, |
1171 | | Data::Read(Read::Request { |
1172 | | address: 0x7890, |
1173 | | quantity: 0x0013 |
1174 | | }) |
1175 | | ); |
1176 | | |
1177 | | assert_eq!( |
1178 | | AppLayerResult::ok(), |
1179 | | state.parse(RD_COILS_ERR_RESP, Direction::ToClient) |
1180 | | ); |
1181 | | assert_eq!(state.transactions.len(), 1); |
1182 | | |
1183 | | let tx = &state.transactions[0]; |
1184 | | let msg = tx.response.as_ref().unwrap(); |
1185 | | assert_eq!( |
1186 | | msg.data, |
1187 | | Data::Exception(Exception { |
1188 | | raw: 255, |
1189 | | code: ExceptionCode::Unknown |
1190 | | }) |
1191 | | ); |
1192 | | assert_eq!(msg.error_flags, ErrorFlags::EXC_CODE); |
1193 | | } |
1194 | | |
1195 | | #[test] |
1196 | | fn fragmentation_1_adu_in_2_tcp_packets() { |
1197 | | let mut state = ModbusState::new(); |
1198 | | assert_eq!( |
1199 | | AppLayerResult::incomplete(0, 12), |
1200 | | state.parse( |
1201 | | &RD_COILS_REQ[0..(RD_COILS_REQ.len() - 3)], |
1202 | | Direction::ToServer |
1203 | | ) |
1204 | | ); |
1205 | | assert_eq!(state.transactions.len(), 0); |
1206 | | assert_eq!( |
1207 | | AppLayerResult::ok(), |
1208 | | state.parse(RD_COILS_REQ, Direction::ToServer) |
1209 | | ); |
1210 | | assert_eq!(state.transactions.len(), 1); |
1211 | | |
1212 | | let tx = &state.transactions[0]; |
1213 | | assert!(&tx.request.is_some()); |
1214 | | let msg = tx.request.as_ref().unwrap(); |
1215 | | assert_eq!(msg.function.code, FunctionCode::RdCoils); |
1216 | | assert_eq!( |
1217 | | msg.data, |
1218 | | Data::Read(Read::Request { |
1219 | | address: 0x7890, |
1220 | | quantity: 0x0013 |
1221 | | }) |
1222 | | ); |
1223 | | } |
1224 | | |
1225 | | #[test] |
1226 | | fn fragmentation_2_adu_in_1_tcp_packet() { |
1227 | | let req = [RD_COILS_REQ, WR_MULT_REG_REQ].concat(); |
1228 | | let resp = [RD_COILS_RESP, WR_MULT_REG_RESP].concat(); |
1229 | | |
1230 | | let mut state = ModbusState::new(); |
1231 | | assert_eq!(AppLayerResult::ok(), state.parse(&req, Direction::ToServer)); |
1232 | | assert_eq!(state.transactions.len(), 2); |
1233 | | |
1234 | | let tx = &state.transactions[0]; |
1235 | | let msg = tx.request.as_ref().unwrap(); |
1236 | | assert_eq!(msg.function.code, FunctionCode::RdCoils); |
1237 | | assert_eq!( |
1238 | | msg.data, |
1239 | | Data::Read(Read::Request { |
1240 | | address: 0x7890, |
1241 | | quantity: 0x0013 |
1242 | | }) |
1243 | | ); |
1244 | | |
1245 | | let tx = &state.transactions[1]; |
1246 | | let msg = tx.request.as_ref().unwrap(); |
1247 | | assert_eq!(msg.function.code, FunctionCode::WrMultRegs); |
1248 | | assert_eq!( |
1249 | | msg.data, |
1250 | | Data::Write(Write::MultReq { |
1251 | | address: 0x0001, |
1252 | | quantity: 0x0002, |
1253 | | data: vec![0x00, 0x0a, 0x01, 0x02] |
1254 | | }) |
1255 | | ); |
1256 | | |
1257 | | assert_eq!( |
1258 | | AppLayerResult::ok(), |
1259 | | state.parse(&resp, Direction::ToClient) |
1260 | | ); |
1261 | | assert_eq!(state.transactions.len(), 2); |
1262 | | |
1263 | | let tx = &state.transactions[0]; |
1264 | | let msg = tx.response.as_ref().unwrap(); |
1265 | | assert_eq!(msg.function.code, FunctionCode::RdCoils); |
1266 | | assert_eq!(msg.data, Data::Read(Read::Response(vec![0xCD, 0x6B, 0x05]))); |
1267 | | |
1268 | | let tx = &state.transactions[1]; |
1269 | | let msg = tx.response.as_ref().unwrap(); |
1270 | | assert_eq!(msg.function.code, FunctionCode::WrMultRegs); |
1271 | | assert_eq!( |
1272 | | msg.data, |
1273 | | Data::Write(Write::Other { |
1274 | | address: 0x0001, |
1275 | | data: 0x0002 |
1276 | | }) |
1277 | | ); |
1278 | | } |
1279 | | |
1280 | | #[test] |
1281 | | fn exceeded_length_request() { |
1282 | | let mut state = ModbusState::new(); |
1283 | | assert_eq!( |
1284 | | AppLayerResult::ok(), |
1285 | | state.parse(EXCEEDED_LEN_WR_MULT_REG_REQ, Direction::ToServer) |
1286 | | ); |
1287 | | |
1288 | | assert_eq!(state.transactions.len(), 1); |
1289 | | let tx = &state.transactions[0]; |
1290 | | let msg = tx.request.as_ref().unwrap(); |
1291 | | assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); |
1292 | | } |
1293 | | |
1294 | | #[test] |
1295 | | fn invalid_pdu_len_req() { |
1296 | | let mut state = ModbusState::new(); |
1297 | | assert_eq!( |
1298 | | AppLayerResult::ok(), |
1299 | | state.parse(INVALID_PDU_WR_MULT_REG_REQ, Direction::ToServer) |
1300 | | ); |
1301 | | |
1302 | | assert_eq!(state.transactions.len(), 1); |
1303 | | |
1304 | | let tx = &state.transactions[0]; |
1305 | | let msg = tx.request.as_ref().unwrap(); |
1306 | | assert_eq!(msg.function.code, FunctionCode::WrMultRegs); |
1307 | | assert_eq!(msg.data, Data::ByteVec(vec![])); |
1308 | | } |
1309 | | |
1310 | | #[test] |
1311 | | fn mask_write_register_request() { |
1312 | | let mut state = ModbusState::new(); |
1313 | | assert_eq!( |
1314 | | AppLayerResult::ok(), |
1315 | | state.parse(MASK_WR_REG_REQ, Direction::ToServer) |
1316 | | ); |
1317 | | assert_eq!(state.transactions.len(), 1); |
1318 | | |
1319 | | let tx = &state.transactions[0]; |
1320 | | let msg = tx.request.as_ref().unwrap(); |
1321 | | assert_eq!(msg.function.code, FunctionCode::MaskWrReg); |
1322 | | assert_eq!( |
1323 | | msg.data, |
1324 | | Data::Write(Write::Mask { |
1325 | | address: 0x0004, |
1326 | | and_mask: 0x00f2, |
1327 | | or_mask: 0x0025 |
1328 | | }) |
1329 | | ); |
1330 | | |
1331 | | assert_eq!( |
1332 | | AppLayerResult::ok(), |
1333 | | state.parse(MASK_WR_REG_RESP, Direction::ToClient) |
1334 | | ); |
1335 | | assert_eq!(state.transactions.len(), 1); |
1336 | | |
1337 | | let tx = &state.transactions[0]; |
1338 | | let msg = tx.response.as_ref().unwrap(); |
1339 | | assert_eq!(msg.function.code, FunctionCode::MaskWrReg); |
1340 | | assert_eq!( |
1341 | | msg.data, |
1342 | | Data::Write(Write::Mask { |
1343 | | address: 0x0004, |
1344 | | and_mask: 0x00f2, |
1345 | | or_mask: 0x0025 |
1346 | | }) |
1347 | | ); |
1348 | | } |
1349 | | |
1350 | | #[test] |
1351 | | fn write_single_register_request() { |
1352 | | let mut state = ModbusState::new(); |
1353 | | assert_eq!( |
1354 | | AppLayerResult::ok(), |
1355 | | state.parse(WR_SINGLE_REG_REQ, Direction::ToServer) |
1356 | | ); |
1357 | | assert_eq!(state.transactions.len(), 1); |
1358 | | |
1359 | | let tx = &state.transactions[0]; |
1360 | | let msg = tx.request.as_ref().unwrap(); |
1361 | | assert_eq!(msg.function.code, FunctionCode::WrSingleReg); |
1362 | | assert_eq!( |
1363 | | msg.data, |
1364 | | Data::Write(Write::Other { |
1365 | | address: 0x0001, |
1366 | | data: 0x0003 |
1367 | | }) |
1368 | | ); |
1369 | | |
1370 | | assert_eq!( |
1371 | | AppLayerResult::ok(), |
1372 | | state.parse(WR_SINGLE_REG_RESP, Direction::ToClient) |
1373 | | ); |
1374 | | assert_eq!(state.transactions.len(), 1); |
1375 | | |
1376 | | let tx = &state.transactions[0]; |
1377 | | let msg = tx.response.as_ref().unwrap(); |
1378 | | assert_eq!(msg.function.code, FunctionCode::WrSingleReg); |
1379 | | assert_eq!( |
1380 | | msg.data, |
1381 | | Data::Write(Write::Other { |
1382 | | address: 0x0001, |
1383 | | data: 0x0003 |
1384 | | }) |
1385 | | ); |
1386 | | } |
1387 | | |
1388 | | #[test] |
1389 | | fn invalid_mask_write_register_request() { |
1390 | | let mut state = ModbusState::new(); |
1391 | | assert_eq!( |
1392 | | AppLayerResult::ok(), |
1393 | | state.parse(INVALID_MASK_WR_REG_REQ, Direction::ToServer) |
1394 | | ); |
1395 | | assert_eq!(state.transactions.len(), 1); |
1396 | | |
1397 | | let tx = &state.transactions[0]; |
1398 | | let msg = tx.request.as_ref().unwrap(); |
1399 | | assert_eq!(msg.function.code, FunctionCode::MaskWrReg); |
1400 | | assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); |
1401 | | assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x04, 0x00, 0xF2])); |
1402 | | |
1403 | | assert_eq!( |
1404 | | AppLayerResult::ok(), |
1405 | | state.parse(MASK_WR_REG_RESP, Direction::ToClient) |
1406 | | ); |
1407 | | assert_eq!(state.transactions.len(), 1); |
1408 | | |
1409 | | let tx = &state.transactions[0]; |
1410 | | let msg = tx.response.as_ref().unwrap(); |
1411 | | assert_eq!(msg.function.code, FunctionCode::MaskWrReg); |
1412 | | assert_eq!( |
1413 | | msg.data, |
1414 | | Data::Write(Write::Mask { |
1415 | | address: 0x0004, |
1416 | | and_mask: 0x00f2, |
1417 | | or_mask: 0x0025 |
1418 | | }) |
1419 | | ); |
1420 | | } |
1421 | | |
1422 | | #[test] |
1423 | | fn invalid_write_single_register_request() { |
1424 | | let mut state = ModbusState::new(); |
1425 | | assert_eq!( |
1426 | | AppLayerResult::ok(), |
1427 | | state.parse(INVALID_WR_SINGLE_REG_REQ, Direction::ToServer) |
1428 | | ); |
1429 | | assert_eq!(state.transactions.len(), 1); |
1430 | | |
1431 | | let tx = &state.transactions[0]; |
1432 | | let msg = tx.request.as_ref().unwrap(); |
1433 | | assert_eq!(msg.function.code, FunctionCode::WrSingleReg); |
1434 | | assert_eq!(msg.error_flags, ErrorFlags::DATA_LENGTH); |
1435 | | assert_eq!(msg.data, Data::ByteVec(vec![0x00, 0x01])); |
1436 | | |
1437 | | assert_eq!( |
1438 | | AppLayerResult::ok(), |
1439 | | state.parse(WR_SINGLE_REG_RESP, Direction::ToClient) |
1440 | | ); |
1441 | | assert_eq!(state.transactions.len(), 1); |
1442 | | |
1443 | | let tx = &state.transactions[0]; |
1444 | | let msg = tx.response.as_ref().unwrap(); |
1445 | | assert_eq!(msg.function.code, FunctionCode::WrSingleReg); |
1446 | | assert_eq!( |
1447 | | msg.data, |
1448 | | Data::Write(Write::Other { |
1449 | | address: 0x0001, |
1450 | | data: 0x0003 |
1451 | | }) |
1452 | | ); |
1453 | | } |
1454 | | |
1455 | | #[test] |
1456 | | fn invalid_function_code() { |
1457 | | let mut state = ModbusState::new(); |
1458 | | assert_eq!( |
1459 | | AppLayerResult::ok(), |
1460 | | state.parse(INVALID_FUNC_CODE, Direction::ToServer) |
1461 | | ); |
1462 | | assert_eq!(state.transactions.len(), 1); |
1463 | | |
1464 | | let tx = &state.transactions[0]; |
1465 | | let msg = tx.request.as_ref().unwrap(); |
1466 | | assert_eq!(msg.function.code, FunctionCode::Unknown); |
1467 | | assert_eq!(msg.data, Data::ByteVec(vec![])); |
1468 | | } |
1469 | | } |