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