Skip to content

Commit 4fbf6fb

Browse files
authored
Add software reset function for UDS scanner (#4605)
1 parent d10b482 commit 4fbf6fb

File tree

3 files changed

+245
-44
lines changed

3 files changed

+245
-44
lines changed

scapy/contrib/automotive/scanner/executor.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,8 @@ def __init__(
6666
socket, # type: Optional[_SocketUnion]
6767
reset_handler=None, # type: Optional[Callable[[], None]]
6868
reconnect_handler=None, # type: Optional[Callable[[], _SocketUnion]] # noqa: E501
69-
test_cases=None,
70-
# type: Optional[List[Union[AutomotiveTestCaseABC, Type[AutomotiveTestCaseABC]]]] # noqa: E501
69+
test_cases=None, # type: Optional[List[Union[AutomotiveTestCaseABC, Type[AutomotiveTestCaseABC]]]] # noqa: E501
70+
software_reset_handler=None, # type: Optional[Callable[[_SocketUnion], None]] # noqa: E501
7171
**kwargs # type: Optional[Dict[str, Any]]
7272
): # type: (...) -> None
7373

@@ -82,6 +82,7 @@ def __init__(
8282
self.target_state = self._initial_ecu_state
8383
self.reset_handler = reset_handler
8484
self.reconnect_handler = reconnect_handler
85+
self.software_reset_handler = software_reset_handler
8586

8687
self.cleanup_functions = list() # type: List[_CleanupCallable]
8788

@@ -152,6 +153,11 @@ def reset_target(self):
152153
log_automotive.info("Target reset")
153154
if self.reset_handler:
154155
self.reset_handler()
156+
elif self.software_reset_handler:
157+
if self.socket and self.socket.closed:
158+
self.reconnect()
159+
if self.socket:
160+
self.software_reset_handler(self.socket)
155161
self.target_state = self._initial_ecu_state
156162

157163
def reconnect(self):

scapy/contrib/automotive/uds_scan.py

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,15 @@
22
# This file is part of Scapy
33
# See https://scapy.net/ for more information
44
# Copyright (C) Nils Weiss <[email protected]>
5-
6-
# scapy.contrib.description = UDS AutomotiveTestCaseExecutor
7-
# scapy.contrib.status = loads
8-
9-
from abc import ABC
10-
import struct
11-
import random
12-
import time
13-
import itertools
145
import copy
156
import inspect
16-
7+
import itertools
8+
import logging
9+
import random
10+
import struct
11+
import time
12+
from abc import ABC
1713
from collections import defaultdict
18-
19-
from scapy.compat import orb
20-
from scapy.contrib.automotive import log_automotive
21-
from scapy.packet import Raw, Packet
22-
from scapy.error import Scapy_Exception
23-
from scapy.contrib.automotive.uds import UDS, UDS_NR, UDS_DSC, UDS_TP, \
24-
UDS_RDBI, UDS_WDBI, UDS_SA, UDS_RC, UDS_IOCBI, UDS_RMBA, UDS_ER, \
25-
UDS_TesterPresentSender, UDS_CC, UDS_RDBPI, UDS_RD, UDS_TD
26-
27-
from scapy.contrib.automotive.ecu import EcuState
28-
from scapy.contrib.automotive.scanner.enumerator import ServiceEnumerator, \
29-
_AutomotiveTestCaseScanResult, _AutomotiveTestCaseFilteredScanResult, \
30-
StateGeneratingServiceEnumerator
31-
from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCaseABC, \
32-
_SocketUnion, _TransitionTuple, StateGenerator
33-
from scapy.contrib.automotive.scanner.configuration import \
34-
AutomotiveTestCaseExecutorConfiguration # noqa: E501
35-
from scapy.contrib.automotive.scanner.graph import _Edge
36-
from scapy.contrib.automotive.scanner.staged_test_case import StagedAutomotiveTestCase # noqa: E501
37-
from scapy.contrib.automotive.scanner.executor import AutomotiveTestCaseExecutor # noqa: E501
38-
39-
# TODO: Refactor this import
40-
from scapy.contrib.automotive.uds_ecu_states import * # noqa: F401, F403
41-
4214
# typing imports
4315
from typing import (
4416
Dict,
@@ -54,6 +26,29 @@
5426
Sequence,
5527
)
5628

29+
from scapy.compat import orb
30+
from scapy.contrib.automotive import log_automotive
31+
from scapy.contrib.automotive.ecu import EcuState
32+
from scapy.contrib.automotive.scanner.configuration import \
33+
AutomotiveTestCaseExecutorConfiguration # noqa: E501
34+
from scapy.contrib.automotive.scanner.enumerator import ServiceEnumerator, \
35+
_AutomotiveTestCaseScanResult, _AutomotiveTestCaseFilteredScanResult, \
36+
StateGeneratingServiceEnumerator
37+
from scapy.contrib.automotive.scanner.executor import AutomotiveTestCaseExecutor # noqa: E501
38+
from scapy.contrib.automotive.scanner.graph import _Edge
39+
from scapy.contrib.automotive.scanner.staged_test_case import StagedAutomotiveTestCase # noqa: E501
40+
from scapy.contrib.automotive.scanner.test_case import AutomotiveTestCaseABC, \
41+
_SocketUnion, _TransitionTuple, StateGenerator
42+
from scapy.contrib.automotive.uds import UDS, UDS_NR, UDS_DSC, UDS_TP, \
43+
UDS_RDBI, UDS_WDBI, UDS_SA, UDS_RC, UDS_IOCBI, UDS_RMBA, UDS_ER, \
44+
UDS_TesterPresentSender, UDS_CC, UDS_RDBPI, UDS_RD, UDS_TD
45+
# TODO: Refactor this import
46+
from scapy.contrib.automotive.uds_ecu_states import * # noqa: F401, F403
47+
from scapy.error import Scapy_Exception
48+
from scapy.packet import Raw, Packet
49+
50+
# scapy.contrib.description = UDS AutomotiveTestCaseExecutor
51+
# scapy.contrib.status = loads
5752

5853
# Definition outside the class UDS_RMBASequentialEnumerator
5954
# to allow pickling
@@ -872,8 +867,8 @@ def _get_table_entry_y(self, tup):
872867
resp = tup[2]
873868
if resp is not None:
874869
return "0x%04x: %s" % \
875-
(tup[1].dataIdentifier,
876-
repr(resp.payload))
870+
(tup[1].dataIdentifier,
871+
repr(resp.payload))
877872
else:
878873
return "0x%04x: No response" % tup[1].dataIdentifier
879874

@@ -1252,3 +1247,24 @@ def default_test_case_clss(self):
12521247
return [UDS_ServiceEnumerator, UDS_DSCEnumerator, UDS_TPEnumerator,
12531248
UDS_SAEnumerator, UDS_WDBISelectiveEnumerator,
12541249
UDS_RMBAEnumerator, UDS_RCEnumerator, UDS_IOCBIEnumerator]
1250+
1251+
1252+
def uds_software_reset(connection, # type: _SocketUnion
1253+
logger=log_automotive # type: logging.Logger
1254+
): # type: (...) -> None
1255+
logger.debug("Reset procedure of target started.")
1256+
resp = connection.sr1(UDS() / UDS_ER(resetType=1),
1257+
timeout=5,
1258+
verbose=False)
1259+
if resp and resp.service != 0x7f:
1260+
logger.debug("Reset procedure of target complete")
1261+
return
1262+
1263+
logger.debug("Couldn't reset target with UDS_ER. "
1264+
"At least try to set target back to DefaultSession")
1265+
resp = connection.sr1(UDS() / UDS_DSC(b"\x01"), verbose=False, timeout=5)
1266+
if resp and resp.service != 0x7f:
1267+
logger.debug("Target in DefaultSession")
1268+
return
1269+
1270+
logger.error("Target not in DefaultSession. Software reset failed.")

test/contrib/automotive/scanner/uds_scanner.uts

Lines changed: 185 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ conf.debug_dissector = False
3030

3131
= Define Testfunction
3232

33-
def executeScannerInVirtualEnvironment(supported_responses, enumerators, unstable_socket=True, **kwargs):
33+
def executeScannerInVirtualEnvironment(supported_responses, enumerators, unstable_socket=True, software_reset=False, **kwargs):
3434
tester_obj_pipe = ObjectPipe(name="TesterPipe")
3535
ecu_obj_pipe = ObjectPipe(name="ECUPipe")
3636
TesterSocket = UnstableSocket if unstable_socket else TestSocket
@@ -58,11 +58,26 @@ def executeScannerInVirtualEnvironment(supported_responses, enumerators, unstabl
5858
sim = threading.Thread(target=answering_machine_thread)
5959
try:
6060
sim.start()
61-
scanner = UDS_Scanner(
62-
tester, reset_handler=reset, reconnect_handler=reconnect,
63-
test_cases=enumerators, timeout=0.1,
64-
retry_if_none_received=True, unittest=True,
65-
**kwargs)
61+
if software_reset:
62+
scanner = UDS_Scanner(
63+
tester,
64+
software_reset_handler=uds_software_reset,
65+
reconnect_handler=reconnect,
66+
test_cases=enumerators,
67+
timeout=0.1,
68+
retry_if_none_received=True,
69+
unittest=True,
70+
**kwargs)
71+
else:
72+
scanner = UDS_Scanner(
73+
tester,
74+
reset_handler=reset,
75+
reconnect_handler=reconnect,
76+
test_cases=enumerators,
77+
timeout=0.1,
78+
retry_if_none_received=True,
79+
unittest=True,
80+
**kwargs)
6681
for i in range(12):
6782
print("Starting scan")
6883
scanner.scan(timeout=10)
@@ -258,6 +273,170 @@ assert "serviceNotSupported received 75 times" in result
258273
assert "serviceNotSupportedInActiveSession received 19 times" in result
259274
assert "securityAccessDenied received 2 times" in result
260275

276+
= Simulate ECU and run Scanner with software resert
277+
278+
responses = ([EcuResponse(None, [UDS()/UDS_DSCPR(b"\x01")])]
279+
+ mEcu.supported_responses)
280+
281+
scanner = executeScannerInVirtualEnvironment(
282+
responses,
283+
[UDS_SA_XOR_Enumerator, UDS_DSCEnumerator, UDS_ServiceEnumerator],
284+
software_reset=True,
285+
UDS_DSCEnumerator_kwargs={"scan_range": range(5), "delay_state_change": 0,
286+
"overwrite_timeout": False},
287+
UDS_SA_XOR_Enumerator_kwargs={"scan_range": range(5)},
288+
UDS_ServiceEnumerator_kwargs={"scan_range": [0x10, 0x11, 0x14, 0x19, 0x22,
289+
0x23, 0x24, 0x27, 0x28, 0x29,
290+
0x2A, 0x2C, 0x2E, 0x2F, 0x31,
291+
0x34, 0x35, 0x36, 0x37, 0x38,
292+
0x3D, 0x3E, 0x83, 0x84, 0x85,
293+
0x87],
294+
"request_length": 1})
295+
296+
scanner.show_testcases()
297+
scanner.show_testcases_status()
298+
assert len(scanner.state_paths) == 6
299+
assert scanner.scan_completed
300+
assert scanner.progress() > 0.95
301+
302+
assert EcuState(session=1) in scanner.final_states
303+
assert EcuState(session=2, tp=1) in scanner.final_states
304+
assert EcuState(session=1, tp=1) in scanner.final_states
305+
assert EcuState(session=3, tp=1) in scanner.final_states
306+
assert EcuState(session=2, tp=1, security_level=2) in scanner.final_states
307+
assert EcuState(session=3, tp=1, security_level=2) in scanner.final_states
308+
309+
#################### UDS_SA_XOR_Enumerator ################
310+
tc = scanner.configuration.test_cases[0]
311+
312+
assert len(tc.results_without_response) < 10
313+
if tc.results_without_response:
314+
tc.show()
315+
316+
assert len(tc.results_with_negative_response) == 24
317+
assert len(tc.results_with_positive_response) >= 6
318+
assert len(tc.scanned_states) == 6
319+
320+
result = tc.show(dump=True)
321+
322+
assert "serviceNotSupportedInActiveSession received 5 times" in result
323+
assert "incorrectMessageLengthOrInvalidFormat received 14 times" in result
324+
325+
################# UDS_DSCEnumerator #####################
326+
tc = scanner.configuration.test_cases[1]
327+
328+
assert len(tc.results_without_response) < 10
329+
if tc.results_without_response:
330+
tc.show()
331+
332+
assert len(tc.results_with_negative_response) == 17
333+
assert len(tc.results_with_positive_response) == 13
334+
assert len(tc.scanned_states) == 6
335+
336+
result = tc.show(dump=True)
337+
338+
assert "incorrectMessageLengthOrInvalidFormat received 14 times" in result
339+
340+
###################### UDS_ServiceEnumerator ###################
341+
tc = scanner.configuration.test_cases[2]
342+
343+
assert len(tc.results_without_response) < 10
344+
if tc.results_without_response:
345+
tc.show()
346+
347+
assert len(tc.results_with_negative_response) == 156
348+
assert len(tc.results_with_positive_response) == 0
349+
assert len(tc.scanned_states) == 6
350+
351+
result = tc.show(dump=True)
352+
353+
assert "incorrectMessageLengthOrInvalidFormat received 34 times" in result
354+
assert "serviceNotSupported received 75 times" in result
355+
assert "serviceNotSupportedInActiveSession received 19 times" in result
356+
assert "securityAccessDenied received 2 times" in result
357+
358+
= Simulate ECU and run Scanner with software resert 2
359+
360+
responses = ([EcuResponse(None, [UDS()/UDS_ERPR(b"\x01")])]
361+
+ mEcu.supported_responses)
362+
363+
scanner = executeScannerInVirtualEnvironment(
364+
responses,
365+
[UDS_SA_XOR_Enumerator, UDS_DSCEnumerator, UDS_ServiceEnumerator],
366+
software_reset=True,
367+
UDS_DSCEnumerator_kwargs={"scan_range": range(5), "delay_state_change": 0,
368+
"overwrite_timeout": False},
369+
UDS_SA_XOR_Enumerator_kwargs={"scan_range": range(5)},
370+
UDS_ServiceEnumerator_kwargs={"scan_range": [0x10, 0x11, 0x14, 0x19, 0x22,
371+
0x23, 0x24, 0x27, 0x28, 0x29,
372+
0x2A, 0x2C, 0x2E, 0x2F, 0x31,
373+
0x34, 0x35, 0x36, 0x37, 0x38,
374+
0x3D, 0x3E, 0x83, 0x84, 0x85,
375+
0x87],
376+
"request_length": 1})
377+
378+
scanner.show_testcases()
379+
scanner.show_testcases_status()
380+
assert len(scanner.state_paths) == 5
381+
assert scanner.scan_completed
382+
assert scanner.progress() > 0.95
383+
384+
assert EcuState(session=1) in scanner.final_states
385+
assert EcuState(session=2, tp=1) in scanner.final_states
386+
assert EcuState(session=3, tp=1) in scanner.final_states
387+
assert EcuState(session=2, tp=1, security_level=2) in scanner.final_states
388+
assert EcuState(session=3, tp=1, security_level=2) in scanner.final_states
389+
390+
#################### UDS_SA_XOR_Enumerator ################
391+
tc = scanner.configuration.test_cases[0]
392+
393+
assert len(tc.results_without_response) < 10
394+
if tc.results_without_response:
395+
tc.show()
396+
397+
assert len(tc.results_with_negative_response) == 19
398+
assert len(tc.results_with_positive_response) >= 6
399+
assert len(tc.scanned_states) == 5
400+
401+
result = tc.show(dump=True)
402+
403+
assert "serviceNotSupportedInActiveSession received 5 times" in result
404+
assert "incorrectMessageLengthOrInvalidFormat received 14 times" in result
405+
406+
################# UDS_DSCEnumerator #####################
407+
tc = scanner.configuration.test_cases[1]
408+
409+
assert len(tc.results_without_response) < 10
410+
if tc.results_without_response:
411+
tc.show()
412+
413+
assert len(tc.results_with_negative_response) == 20
414+
assert len(tc.results_with_positive_response) == 5
415+
assert len(tc.scanned_states) == 5
416+
417+
result = tc.show(dump=True)
418+
419+
assert "incorrectMessageLengthOrInvalidFormat received 20 times" in result
420+
421+
###################### UDS_ServiceEnumerator ###################
422+
tc = scanner.configuration.test_cases[2]
423+
424+
assert len(tc.results_without_response) < 10
425+
if tc.results_without_response:
426+
tc.show()
427+
428+
assert len(tc.results_with_negative_response) == 130
429+
assert len(tc.results_with_positive_response) == 0
430+
assert len(tc.scanned_states) == 5
431+
432+
result = tc.show(dump=True)
433+
434+
assert "incorrectMessageLengthOrInvalidFormat received 34 times" in result
435+
assert "serviceNotSupported received 75 times" in result
436+
assert "serviceNotSupportedInActiveSession received 19 times" in result
437+
assert "securityAccessDenied received 2 times" in result
438+
439+
261440
= UDS_ServiceEnumerator
262441

263442
def req_handler(resp, req):

0 commit comments

Comments
 (0)