py-uds-demo 26.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- py_uds_demo/__init__.py +0 -0
- py_uds_demo/__main__.py +57 -0
- py_uds_demo/core/__init__.py +0 -0
- py_uds_demo/core/client.py +80 -0
- py_uds_demo/core/server.py +227 -0
- py_uds_demo/core/utils/__init__.py +0 -0
- py_uds_demo/core/utils/helpers.py +314 -0
- py_uds_demo/core/utils/responses.py +55 -0
- py_uds_demo/core/utils/services/__init__.py +0 -0
- py_uds_demo/core/utils/services/data_transmission.py +398 -0
- py_uds_demo/core/utils/services/diagnostic_and_commmunication_management.py +755 -0
- py_uds_demo/core/utils/services/input_output_contol.py +63 -0
- py_uds_demo/core/utils/services/negative_response.py +1 -0
- py_uds_demo/core/utils/services/remote_activation_of_routine.py +80 -0
- py_uds_demo/core/utils/services/stored_data_transmission.py +132 -0
- py_uds_demo/core/utils/services/upload_download.py +189 -0
- py_uds_demo/interface/__init__.py +0 -0
- py_uds_demo/interface/api.py +30 -0
- py_uds_demo/interface/cli.py +51 -0
- py_uds_demo/interface/gui.py +83 -0
- py_uds_demo/interface/web.py +422 -0
- py_uds_demo-26.0.1.dist-info/METADATA +53 -0
- py_uds_demo-26.0.1.dist-info/RECORD +24 -0
- py_uds_demo-26.0.1.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,755 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
import datetime
|
|
3
|
+
from time import sleep
|
|
4
|
+
from random import randint
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from py_uds_demo.core.server import UdsServer
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class DiagnosticSessionControl:
|
|
12
|
+
"""
|
|
13
|
+
Handles Diagnostic Session Control (0x10) service requests.
|
|
14
|
+
|
|
15
|
+
What:
|
|
16
|
+
This service is used to switch the server (ECU) to a specific
|
|
17
|
+
diagnostic session. Each session can grant different levels of access
|
|
18
|
+
to diagnostic services and data.
|
|
19
|
+
|
|
20
|
+
Why:
|
|
21
|
+
Different tasks require different security levels. For example, reading
|
|
22
|
+
basic data might be allowed in a default session, but reprogramming
|
|
23
|
+
the ECU would require switching to a programming session with higher
|
|
24
|
+
security access.
|
|
25
|
+
|
|
26
|
+
How:
|
|
27
|
+
The client sends a request with the SID 0x10 followed by a single byte
|
|
28
|
+
sub-function indicating the desired session.
|
|
29
|
+
|
|
30
|
+
Real-world example:
|
|
31
|
+
A technician uses a diagnostic tool to connect to a car. The tool
|
|
32
|
+
starts in the default session, which allows reading error codes. To
|
|
33
|
+
perform a software update, the tool requests to switch to the
|
|
34
|
+
programming session. If the security checks pass, the ECU switches
|
|
35
|
+
to the programming session, allowing the update to proceed.
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
uds_server: The UDS server instance.
|
|
39
|
+
active_session: The currently active diagnostic session.
|
|
40
|
+
supported_subfunctions: A list of supported sub-function identifiers.
|
|
41
|
+
P2_HIGH (int): P2 timing parameter high byte.
|
|
42
|
+
P2_LOW (int): P2 timing parameter low byte.
|
|
43
|
+
P2_STAR_HIGH (int): P2* timing parameter high byte.
|
|
44
|
+
P2_STAR_LOW (int): P2* timing parameter low byte.
|
|
45
|
+
tester_present_active (bool): True if Tester Present is active.
|
|
46
|
+
session_timeout (int): The timeout for non-default sessions in seconds.
|
|
47
|
+
"""
|
|
48
|
+
def __init__(self, uds_server: 'UdsServer'):
|
|
49
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
50
|
+
self.active_session = self.uds_server.SFID.DEFAULT_SESSION
|
|
51
|
+
self.supported_subfunctions = [
|
|
52
|
+
self.uds_server.SFID.DEFAULT_SESSION,
|
|
53
|
+
self.uds_server.SFID.PROGRAMMING_SESSION,
|
|
54
|
+
self.uds_server.SFID.EXTENDED_SESSION,
|
|
55
|
+
self.uds_server.SFID.SAFETY_SYSTEM_DIAGNOSTIC_SESSION,
|
|
56
|
+
]
|
|
57
|
+
# P2 = 50 ms
|
|
58
|
+
self.P2_HIGH = 0x00
|
|
59
|
+
self.P2_LOW = 0x32
|
|
60
|
+
# P2* = 5000 ms
|
|
61
|
+
self.P2_STAR_HIGH = 0x13
|
|
62
|
+
self.P2_STAR_LOW = 0x88
|
|
63
|
+
self.tester_present_active = False
|
|
64
|
+
self.session_timeout = 5 # 5 seconds
|
|
65
|
+
self.last_session_change_time = datetime.datetime.now()
|
|
66
|
+
self.thread_event = threading.Event()
|
|
67
|
+
self.session_thread = threading.Thread(target=self._start_active_session_timeout_thread, daemon=True)
|
|
68
|
+
self.session_thread.start()
|
|
69
|
+
|
|
70
|
+
def __del__(self):
|
|
71
|
+
"""Stops the session timeout thread."""
|
|
72
|
+
self.thread_event.set()
|
|
73
|
+
self.session_thread.join(1)
|
|
74
|
+
|
|
75
|
+
def process_request(self, data_stream: list) -> list:
|
|
76
|
+
"""
|
|
77
|
+
Processes a Diagnostic Session Control request.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
data_stream: The request data stream.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
A list of bytes representing the response.
|
|
84
|
+
"""
|
|
85
|
+
if len(data_stream) != 2:
|
|
86
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
87
|
+
self.uds_server.SID.DSC, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
88
|
+
)
|
|
89
|
+
sfid = data_stream[1]
|
|
90
|
+
if sfid not in self.supported_subfunctions:
|
|
91
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
92
|
+
self.uds_server.SID.DSC, self.uds_server.NRC.SUB_FUNCTION_NOT_SUPPORTED
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
self.active_session = sfid
|
|
96
|
+
self.last_session_change_time = datetime.datetime.now()
|
|
97
|
+
return self.uds_server.positive_response.report_positive_response(
|
|
98
|
+
self.uds_server.SID.DSC, [sfid, self.P2_HIGH, self.P2_LOW, self.P2_STAR_HIGH, self.P2_STAR_LOW]
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def _start_active_session_timeout_thread(self):
|
|
102
|
+
"""
|
|
103
|
+
A thread that monitors the active session and reverts to the default
|
|
104
|
+
session if no Tester Present message is received within the timeout.
|
|
105
|
+
"""
|
|
106
|
+
while not self.thread_event.is_set():
|
|
107
|
+
if self.tester_present_active:
|
|
108
|
+
sleep(0.1)
|
|
109
|
+
continue
|
|
110
|
+
now = datetime.datetime.now()
|
|
111
|
+
elapsed = (now - self.last_session_change_time).total_seconds()
|
|
112
|
+
if self.active_session != self.uds_server.SFID.DEFAULT_SESSION and elapsed >= self.session_timeout:
|
|
113
|
+
self.active_session = self.uds_server.SFID.DEFAULT_SESSION
|
|
114
|
+
self.last_session_change_time = now
|
|
115
|
+
sleep(0.1)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
class EcuReset:
|
|
119
|
+
"""
|
|
120
|
+
Handles ECU Reset (0x11) service requests.
|
|
121
|
+
|
|
122
|
+
What:
|
|
123
|
+
This service is used to restart an ECU. Different types of resets can
|
|
124
|
+
be performed, such as a hard reset (simulating a power cycle) or a
|
|
125
|
+
soft reset (re-initializing software).
|
|
126
|
+
|
|
127
|
+
Why:
|
|
128
|
+
An ECU reset is often necessary to recover an ECU from a faulty
|
|
129
|
+
state, to apply new settings, or to complete a software update
|
|
130
|
+
process.
|
|
131
|
+
|
|
132
|
+
How:
|
|
133
|
+
The client sends a request with the SID 0x11 followed by a single byte
|
|
134
|
+
sub-function indicating the desired reset type.
|
|
135
|
+
|
|
136
|
+
Real-world example:
|
|
137
|
+
After successfully flashing a new firmware version to an ECU, a
|
|
138
|
+
technician sends an ECU Reset request with the 'hardReset' sub-function.
|
|
139
|
+
This forces the ECU to restart, loading the new firmware and
|
|
140
|
+
completing the update process.
|
|
141
|
+
|
|
142
|
+
Attributes:
|
|
143
|
+
uds_server: The UDS server instance.
|
|
144
|
+
supported_subfunctions: A list of supported sub-function identifiers.
|
|
145
|
+
"""
|
|
146
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
147
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
148
|
+
self.supported_subfunctions = [
|
|
149
|
+
self.uds_server.SFID.HARD_RESET,
|
|
150
|
+
self.uds_server.SFID.KEY_ON_OFF_RESET,
|
|
151
|
+
self.uds_server.SFID.SOFT_RESET,
|
|
152
|
+
]
|
|
153
|
+
|
|
154
|
+
def process_request(self, data_stream: list) -> list:
|
|
155
|
+
"""
|
|
156
|
+
Processes an ECU Reset request.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
data_stream: The request data stream.
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
A list of bytes representing the response.
|
|
163
|
+
"""
|
|
164
|
+
if len(data_stream) != 2:
|
|
165
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
166
|
+
self.uds_server.SID.ER, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
167
|
+
)
|
|
168
|
+
reset_type = data_stream[1]
|
|
169
|
+
if reset_type not in self.supported_subfunctions:
|
|
170
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
171
|
+
self.uds_server.SID.ER, self.uds_server.NRC.SUB_FUNCTION_NOT_SUPPORTED
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
if (
|
|
175
|
+
self.uds_server.diagnostic_session_control.active_session == self.uds_server.SFID.PROGRAMMING_SESSION
|
|
176
|
+
and reset_type != self.uds_server.SFID.HARD_RESET
|
|
177
|
+
):
|
|
178
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
179
|
+
self.uds_server.SID.ER, self.uds_server.NRC.REQUEST_OUT_OF_RANGE
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
self.uds_server.diagnostic_session_control.active_session = self.uds_server.SFID.DEFAULT_SESSION
|
|
183
|
+
self.uds_server.security_access.seed_sent = False
|
|
184
|
+
self.uds_server.security_access.security_unlock_success = False
|
|
185
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.ER, [reset_type])
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
class SecurityAccess:
|
|
189
|
+
"""
|
|
190
|
+
Handles Security Access (0x27) service requests.
|
|
191
|
+
|
|
192
|
+
What:
|
|
193
|
+
This service provides a security mechanism to protect certain services
|
|
194
|
+
from unauthorized access. It uses a seed-and-key exchange to
|
|
195
|
+
authenticate the client.
|
|
196
|
+
|
|
197
|
+
Why:
|
|
198
|
+
It's crucial to prevent unauthorized users from accessing critical
|
|
199
|
+
ECU functions, such as flashing new software, changing the VIN, or
|
|
200
|
+
modifying calibration data.
|
|
201
|
+
|
|
202
|
+
How:
|
|
203
|
+
The client requests a 'seed' from the ECU. Using a secret algorithm,
|
|
204
|
+
the client calculates a 'key' from the seed and sends it back. If the
|
|
205
|
+
key is correct, the ECU grants access to protected services.
|
|
206
|
+
|
|
207
|
+
Real-world example:
|
|
208
|
+
A manufacturer protects the engine's fuel map from being modified.
|
|
209
|
+
To change the fuel map, a diagnostic tool must first use the Security
|
|
210
|
+
Access service. The tool requests a seed, calculates the key, and
|
|
211
|
+
sends it back. If successful, the tool can then use the Write Data
|
|
212
|
+
By Identifier service to update the fuel map.
|
|
213
|
+
|
|
214
|
+
Attributes:
|
|
215
|
+
uds_server: The UDS server instance.
|
|
216
|
+
seed_value: The last generated seed.
|
|
217
|
+
seed_sent: True if a seed has been sent to the client.
|
|
218
|
+
security_unlock_success: True if the ECU is unlocked.
|
|
219
|
+
supported_subfunctions: A list of supported sub-function identifiers.
|
|
220
|
+
supported_sessions: A list of sessions in which security access is allowed.
|
|
221
|
+
"""
|
|
222
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
223
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
224
|
+
self.seed_value = []
|
|
225
|
+
self.seed_sent = False
|
|
226
|
+
self.security_unlock_success = False
|
|
227
|
+
self.supported_subfunctions = [
|
|
228
|
+
self.uds_server.SFID.REQUEST_SEED,
|
|
229
|
+
self.uds_server.SFID.SEND_KEY,
|
|
230
|
+
]
|
|
231
|
+
self.supported_sessions = [
|
|
232
|
+
self.uds_server.SFID.PROGRAMMING_SESSION,
|
|
233
|
+
self.uds_server.SFID.EXTENDED_SESSION,
|
|
234
|
+
]
|
|
235
|
+
|
|
236
|
+
def process_request(self, data_stream: list) -> list:
|
|
237
|
+
"""
|
|
238
|
+
Processes a Security Access request.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
data_stream: The request data stream.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
A list of bytes representing the response.
|
|
245
|
+
"""
|
|
246
|
+
if len(data_stream) < 2:
|
|
247
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
248
|
+
self.uds_server.SID.SA, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
249
|
+
)
|
|
250
|
+
if self.uds_server.diagnostic_session_control.active_session not in self.supported_sessions:
|
|
251
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
252
|
+
self.uds_server.SID.SA, self.uds_server.NRC.CONDITIONS_NOT_CORRECT
|
|
253
|
+
)
|
|
254
|
+
sfid = data_stream[1]
|
|
255
|
+
if sfid not in self.supported_subfunctions:
|
|
256
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
257
|
+
self.uds_server.SID.SA, self.uds_server.NRC.SUB_FUNCTION_NOT_SUPPORTED
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
if (sfid % 2 == 0 and not self.seed_sent) or (sfid % 2 == 1 and self.seed_sent) or self.security_unlock_success:
|
|
261
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
262
|
+
self.uds_server.SID.SA, self.uds_server.NRC.REQUEST_SEQUENCE_ERROR
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if sfid % 2 == 1:
|
|
266
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.SA, [sfid] + self._get_seed())
|
|
267
|
+
|
|
268
|
+
if len(data_stream) != 6:
|
|
269
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
270
|
+
self.uds_server.SID.SA, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
if self._check_key(data_stream[2:]):
|
|
274
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.SA, [sfid])
|
|
275
|
+
else:
|
|
276
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
277
|
+
self.uds_server.SID.SA, self.uds_server.NRC.SECURITY_ACCESS_DENIED
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
def _get_seed(self) -> list:
|
|
281
|
+
"""Generates a random seed."""
|
|
282
|
+
self.seed_value = [randint(0, 255) for _ in range(4)]
|
|
283
|
+
self.seed_sent = True
|
|
284
|
+
return self.seed_value
|
|
285
|
+
|
|
286
|
+
def _check_key(self, key: list) -> bool:
|
|
287
|
+
"""
|
|
288
|
+
Checks if the provided key is valid.
|
|
289
|
+
|
|
290
|
+
Args:
|
|
291
|
+
key: The key provided by the client.
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
True if the key is valid, False otherwise.
|
|
295
|
+
"""
|
|
296
|
+
internal_key = (
|
|
297
|
+
(self.seed_value[0] << 24)
|
|
298
|
+
| (self.seed_value[1] << 16)
|
|
299
|
+
| (self.seed_value[2] << 8)
|
|
300
|
+
| self.seed_value[3]
|
|
301
|
+
) | 0x11223344
|
|
302
|
+
received_key = (
|
|
303
|
+
(key[0] << 24)
|
|
304
|
+
| (key[1] << 16)
|
|
305
|
+
| (key[2] << 8)
|
|
306
|
+
| key[3]
|
|
307
|
+
)
|
|
308
|
+
if internal_key == received_key:
|
|
309
|
+
self.security_unlock_success = True
|
|
310
|
+
return True
|
|
311
|
+
return False
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class CommunicationControl:
|
|
315
|
+
"""
|
|
316
|
+
Handles Communication Control (0x28) service requests.
|
|
317
|
+
|
|
318
|
+
What:
|
|
319
|
+
This service is used to control the communication of the server (ECU)
|
|
320
|
+
on the network. It can enable or disable the transmission and/or
|
|
321
|
+
reception of certain types of messages.
|
|
322
|
+
|
|
323
|
+
Why:
|
|
324
|
+
It's useful for isolating an ECU during diagnostics or to prevent
|
|
325
|
+
interference during sensitive operations like software flashing. For
|
|
326
|
+
example, you can stop an ECU from sending messages that might
|
|
327
|
+
disrupt other nodes on the network while you are reprogramming it.
|
|
328
|
+
|
|
329
|
+
How:
|
|
330
|
+
The client sends a request with the SID 0x28, a sub-function to
|
|
331
|
+
specify the control type (e.g., enableRxAndTx, disableRx), and a
|
|
332
|
+
parameter for the communication type (e.g., normal communication,
|
|
333
|
+
network management).
|
|
334
|
+
|
|
335
|
+
Real-world example:
|
|
336
|
+
Before updating the firmware on an airbag control unit, a technician's
|
|
337
|
+
tool sends a Communication Control request to disable the transmission
|
|
338
|
+
of normal messages from that ECU. This prevents the ECU from sending
|
|
339
|
+
any potentially conflicting messages during the update. Once the
|
|
340
|
+
update is complete, the tool re-enables communication.
|
|
341
|
+
|
|
342
|
+
Attributes:
|
|
343
|
+
uds_server: The UDS server instance.
|
|
344
|
+
supported_subfunctions: A list of supported sub-function identifiers.
|
|
345
|
+
supported_communication_types: A list of supported communication types.
|
|
346
|
+
supported_sessions: A list of sessions in which communication control is allowed.
|
|
347
|
+
"""
|
|
348
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
349
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
350
|
+
self.NORMAL_COMMUNICATION_MESSAGES = 0x01
|
|
351
|
+
self.NETWORK_MANAGEMENT_MESSAGES = 0x01
|
|
352
|
+
self.BOTH_TYPES = 0x01
|
|
353
|
+
self.supported_subfunctions = [
|
|
354
|
+
self.uds_server.SFID.ENABLE_RX_AND_TX,
|
|
355
|
+
self.uds_server.SFID.ENABLE_RX_AND_DISABLE_TX,
|
|
356
|
+
self.uds_server.SFID.DISABLE_RX_AND_ENABLE_TX,
|
|
357
|
+
self.uds_server.SFID.DISABLE_RX_AND_TX,
|
|
358
|
+
]
|
|
359
|
+
self.supported_communication_types = [
|
|
360
|
+
self.NORMAL_COMMUNICATION_MESSAGES,
|
|
361
|
+
self.NETWORK_MANAGEMENT_MESSAGES,
|
|
362
|
+
self.BOTH_TYPES,
|
|
363
|
+
]
|
|
364
|
+
self.supported_sessions = [
|
|
365
|
+
self.uds_server.SFID.PROGRAMMING_SESSION,
|
|
366
|
+
self.uds_server.SFID.EXTENDED_SESSION,
|
|
367
|
+
]
|
|
368
|
+
|
|
369
|
+
def process_request(self, data_stream: list) -> list:
|
|
370
|
+
"""
|
|
371
|
+
Processes a Communication Control request.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
data_stream: The request data stream.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
A list of bytes representing the response.
|
|
378
|
+
"""
|
|
379
|
+
if not (len(data_stream) >= 3):
|
|
380
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
381
|
+
self.uds_server.SID.CC, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
382
|
+
)
|
|
383
|
+
if self.uds_server.diagnostic_session_control.active_session not in self.supported_sessions:
|
|
384
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
385
|
+
self.uds_server.SID.CC, self.uds_server.NRC.CONDITIONS_NOT_CORRECT
|
|
386
|
+
)
|
|
387
|
+
sfid = data_stream[1]
|
|
388
|
+
if sfid not in self.supported_subfunctions:
|
|
389
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
390
|
+
self.uds_server.SID.CC, self.uds_server.NRC.SUB_FUNCTION_NOT_SUPPORTED
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
communication_type = data_stream[2]
|
|
394
|
+
if communication_type not in self.supported_communication_types:
|
|
395
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
396
|
+
self.uds_server.SID.CC, self.uds_server.NRC.REQUEST_OUT_OF_RANGE
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.CC, data_stream[1:])
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
class TesterPresent:
|
|
403
|
+
"""
|
|
404
|
+
Handles Tester Present (0x3E) service requests.
|
|
405
|
+
|
|
406
|
+
What:
|
|
407
|
+
This service is used to indicate to the server (ECU) that a client
|
|
408
|
+
is still connected and that the current diagnostic session should
|
|
409
|
+
remain active.
|
|
410
|
+
|
|
411
|
+
Why:
|
|
412
|
+
If there is no communication for a certain period, the ECU will
|
|
413
|
+
automatically time out and return to the default diagnostic session.
|
|
414
|
+
The Tester Present service prevents this from happening.
|
|
415
|
+
|
|
416
|
+
How:
|
|
417
|
+
The client periodically sends a request with the SID 0x3E. A sub-function
|
|
418
|
+
can be used to either request a response from the server or suppress it.
|
|
419
|
+
|
|
420
|
+
Real-world example:
|
|
421
|
+
A technician is monitoring live data from a sensor, which requires
|
|
422
|
+
the ECU to be in the extended diagnostic session. To prevent the
|
|
423
|
+
session from timing out while they are observing the data, the
|
|
424
|
+
diagnostic tool sends a Tester Present message every few seconds.
|
|
425
|
+
|
|
426
|
+
Attributes:
|
|
427
|
+
uds_server: The UDS server instance.
|
|
428
|
+
tester_present_request_received: True if a Tester Present request has been received.
|
|
429
|
+
supported_subfunctions: A list of supported sub-function identifiers.
|
|
430
|
+
"""
|
|
431
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
432
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
433
|
+
self.tester_present_request_received = False
|
|
434
|
+
self.supported_subfunctions = [
|
|
435
|
+
self.uds_server.SFID.ZERO_SUB_FUNCTION,
|
|
436
|
+
self.uds_server.SFID.ZERO_SUB_FUNCTION_SUPRESS_RESPONSE,
|
|
437
|
+
]
|
|
438
|
+
|
|
439
|
+
def process_request(self, data_stream: list) -> list:
|
|
440
|
+
"""
|
|
441
|
+
Processes a Tester Present request.
|
|
442
|
+
|
|
443
|
+
Args:
|
|
444
|
+
data_stream: The request data stream.
|
|
445
|
+
|
|
446
|
+
Returns:
|
|
447
|
+
A list of bytes representing the response, or an empty list if the
|
|
448
|
+
response is suppressed.
|
|
449
|
+
"""
|
|
450
|
+
if len(data_stream) != 2:
|
|
451
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
452
|
+
self.uds_server.SID.TP, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
453
|
+
)
|
|
454
|
+
sfid = data_stream[1]
|
|
455
|
+
if sfid not in self.supported_subfunctions:
|
|
456
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
457
|
+
self.uds_server.SID.TP, self.uds_server.NRC.SUB_FUNCTION_NOT_SUPPORTED
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
self.tester_present_request_received = True
|
|
461
|
+
if sfid == self.uds_server.SFID.ZERO_SUB_FUNCTION:
|
|
462
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.TP, data_stream[1:])
|
|
463
|
+
else:
|
|
464
|
+
return [] # Suppress response
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class AccessTimingParameter:
|
|
468
|
+
"""
|
|
469
|
+
Handles Access Timing Parameter (0x83) service requests.
|
|
470
|
+
|
|
471
|
+
What:
|
|
472
|
+
This service is used to read or write the communication timing
|
|
473
|
+
parameters between the client and the server.
|
|
474
|
+
|
|
475
|
+
Why:
|
|
476
|
+
In certain situations, like on slow or high-latency networks, it may
|
|
477
|
+
be necessary to adjust the default timing parameters (e.g., P2, P2*)
|
|
478
|
+
to ensure reliable communication.
|
|
479
|
+
|
|
480
|
+
How:
|
|
481
|
+
The client can send a request to read the current timing parameters or
|
|
482
|
+
to set new ones.
|
|
483
|
+
|
|
484
|
+
Real-world example:
|
|
485
|
+
A diagnostic tool is connected to an ECU over a wireless network, which
|
|
486
|
+
has a higher latency than a direct wired connection. To prevent
|
|
487
|
+
communication timeouts, the tool uses this service to extend the
|
|
488
|
+
timing parameters, allowing more time for responses.
|
|
489
|
+
|
|
490
|
+
Attributes:
|
|
491
|
+
uds_server: The UDS server instance.
|
|
492
|
+
supported_subfunctions: A list of supported sub-function identifiers.
|
|
493
|
+
timing_parameters: A dictionary of timing parameters.
|
|
494
|
+
"""
|
|
495
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
496
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
497
|
+
self.supported_subfunctions = [
|
|
498
|
+
self.uds_server.SFID.READ_EXTENDED_TIMING_PARAMETER_SET,
|
|
499
|
+
self.uds_server.SFID.SET_TIMING_PARAMETERS_TO_DEFAULT_VALUE,
|
|
500
|
+
self.uds_server.SFID.READ_CURRENTLY_ACTIVE_TIMING_PARAMETERS,
|
|
501
|
+
self.uds_server.SFID.SET_TIMING_PARAMETERS_TO_GIVEN_VALUES,
|
|
502
|
+
]
|
|
503
|
+
self.timing_parameters = {
|
|
504
|
+
"P2_HIGH": 0x00,
|
|
505
|
+
"P2_LOW": 0x32,
|
|
506
|
+
"P2_STAR_HIGH": 0x13,
|
|
507
|
+
"P2_STAR_LOW": 0x88,
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
def process_request(self, data_stream: list) -> list:
|
|
511
|
+
"""
|
|
512
|
+
Processes an Access Timing Parameter request.
|
|
513
|
+
|
|
514
|
+
Args:
|
|
515
|
+
data_stream: The request data stream.
|
|
516
|
+
|
|
517
|
+
Returns:
|
|
518
|
+
A list of bytes representing the response.
|
|
519
|
+
"""
|
|
520
|
+
if len(data_stream) < 2:
|
|
521
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
522
|
+
self.uds_server.SID.ATP, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
sfid = data_stream[1]
|
|
526
|
+
if sfid not in self.supported_subfunctions:
|
|
527
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
528
|
+
self.uds_server.SID.ATP, self.uds_server.NRC.SUB_FUNCTION_NOT_SUPPORTED
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
if sfid == self.uds_server.SFID.READ_EXTENDED_TIMING_PARAMETER_SET or sfid == self.uds_server.SFID.READ_CURRENTLY_ACTIVE_TIMING_PARAMETERS:
|
|
532
|
+
return self.uds_server.positive_response.report_positive_response(
|
|
533
|
+
self.uds_server.SID.ATP, [sfid] + list(self.timing_parameters.values())
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
if sfid == self.uds_server.SFID.SET_TIMING_PARAMETERS_TO_DEFAULT_VALUE:
|
|
537
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.ATP, [sfid])
|
|
538
|
+
|
|
539
|
+
if sfid == self.uds_server.SFID.SET_TIMING_PARAMETERS_TO_GIVEN_VALUES:
|
|
540
|
+
if len(data_stream) != 6:
|
|
541
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
542
|
+
self.uds_server.SID.ATP, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
self.timing_parameters["P2_HIGH"] = data_stream[2]
|
|
546
|
+
self.timing_parameters["P2_LOW"] = data_stream[3]
|
|
547
|
+
self.timing_parameters["P2_STAR_HIGH"] = data_stream[4]
|
|
548
|
+
self.timing_parameters["P2_STAR_LOW"] = data_stream[5]
|
|
549
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.ATP, [sfid])
|
|
550
|
+
|
|
551
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
552
|
+
self.uds_server.SID.ATP, self.uds_server.NRC.REQUEST_OUT_OF_RANGE
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
|
|
556
|
+
class SecuredDataTransmission:
|
|
557
|
+
"""
|
|
558
|
+
Handles Secured Data Transmission (0x84) service requests.
|
|
559
|
+
|
|
560
|
+
What:
|
|
561
|
+
This service is used to transmit data securely between the client and
|
|
562
|
+
the server, providing cryptographic protection for the data.
|
|
563
|
+
|
|
564
|
+
Why:
|
|
565
|
+
It's used when sensitive data needs to be exchanged over a potentially
|
|
566
|
+
insecure network, ensuring confidentiality and integrity.
|
|
567
|
+
|
|
568
|
+
How:
|
|
569
|
+
The implementation details, including the cryptographic algorithms,
|
|
570
|
+
are typically manufacturer-specific.
|
|
571
|
+
|
|
572
|
+
Note:
|
|
573
|
+
This service is not fully implemented in this simulator.
|
|
574
|
+
"""
|
|
575
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
576
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
577
|
+
|
|
578
|
+
def process_request(self, data_stream: list) -> list:
|
|
579
|
+
"""
|
|
580
|
+
Processes a Secured Data Transmission request.
|
|
581
|
+
|
|
582
|
+
Args:
|
|
583
|
+
data_stream: The request data stream.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
A negative response, as this service is not supported.
|
|
587
|
+
"""
|
|
588
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
589
|
+
self.uds_server.SID.SDT, self.uds_server.NRC.SERVICE_NOT_SUPPORTED
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
class ControlDtcSetting:
|
|
594
|
+
"""
|
|
595
|
+
Handles Control DTC Setting (0x85) service requests.
|
|
596
|
+
|
|
597
|
+
What:
|
|
598
|
+
This service is used to enable or disable the setting of Diagnostic
|
|
599
|
+
Trouble Codes (DTCs) in the server (ECU).
|
|
600
|
+
|
|
601
|
+
Why:
|
|
602
|
+
During maintenance or testing, some actions might trigger false DTCs.
|
|
603
|
+
This service allows a client to temporarily disable DTC reporting to
|
|
604
|
+
avoid filling the fault memory with irrelevant codes.
|
|
605
|
+
|
|
606
|
+
How:
|
|
607
|
+
The client sends a request with the SID 0x85 and a sub-function to
|
|
608
|
+
either enable (0x01) or disable (0x02) DTC setting.
|
|
609
|
+
|
|
610
|
+
Real-world example:
|
|
611
|
+
A technician is replacing a sensor. To prevent the ECU from storing
|
|
612
|
+
a DTC for the disconnected sensor during the replacement process,
|
|
613
|
+
they first use this service to disable DTC setting. After the new
|
|
614
|
+
sensor is installed, they re-enable it.
|
|
615
|
+
|
|
616
|
+
Attributes:
|
|
617
|
+
uds_server: The UDS server instance.
|
|
618
|
+
supported_subfunctions: A list of supported sub-function identifiers.
|
|
619
|
+
supported_sessions: A list of sessions in which this service is allowed.
|
|
620
|
+
dtc_setting: The current DTC setting (ON or OFF).
|
|
621
|
+
"""
|
|
622
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
623
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
624
|
+
self.supported_subfunctions = [
|
|
625
|
+
self.uds_server.SFID.ON,
|
|
626
|
+
self.uds_server.SFID.OFF,
|
|
627
|
+
]
|
|
628
|
+
self.supported_sessions = [
|
|
629
|
+
self.uds_server.SFID.PROGRAMMING_SESSION,
|
|
630
|
+
self.uds_server.SFID.EXTENDED_SESSION,
|
|
631
|
+
]
|
|
632
|
+
self.dtc_setting = self.uds_server.SFID.ON
|
|
633
|
+
|
|
634
|
+
def process_request(self, data_stream: list) -> list:
|
|
635
|
+
"""
|
|
636
|
+
Processes a Control DTC Setting request.
|
|
637
|
+
|
|
638
|
+
Args:
|
|
639
|
+
data_stream: The request data stream.
|
|
640
|
+
|
|
641
|
+
Returns:
|
|
642
|
+
A list of bytes representing the response.
|
|
643
|
+
"""
|
|
644
|
+
if len(data_stream) != 2:
|
|
645
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
646
|
+
self.uds_server.SID.CDTCS, self.uds_server.NRC.INCORRECT_MESSAGE_LENGTH_OR_INVALID_FORMAT
|
|
647
|
+
)
|
|
648
|
+
if self.uds_server.diagnostic_session_control.active_session not in self.supported_sessions:
|
|
649
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
650
|
+
self.uds_server.SID.CDTCS, self.uds_server.NRC.CONDITIONS_NOT_CORRECT
|
|
651
|
+
)
|
|
652
|
+
sfid = data_stream[1]
|
|
653
|
+
if sfid not in self.supported_subfunctions:
|
|
654
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
655
|
+
self.uds_server.SID.CDTCS, self.uds_server.NRC.SUB_FUNCTION_NOT_SUPPORTED
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
self.dtc_setting = sfid
|
|
659
|
+
return self.uds_server.positive_response.report_positive_response(self.uds_server.SID.CDTCS, data_stream[1:])
|
|
660
|
+
|
|
661
|
+
|
|
662
|
+
class ResponseOnEvent:
|
|
663
|
+
"""
|
|
664
|
+
Handles Response On Event (0x86) service requests.
|
|
665
|
+
|
|
666
|
+
What:
|
|
667
|
+
This service allows a client to request that the server (ECU)
|
|
668
|
+
automatically sends a response when a specific event occurs, instead
|
|
669
|
+
of the client having to poll for it.
|
|
670
|
+
|
|
671
|
+
Why:
|
|
672
|
+
It's useful for monitoring real-time events without the overhead of
|
|
673
|
+
continuous polling. For example, a client can be notified immediately
|
|
674
|
+
when a DTC is set or a sensor value crosses a certain threshold.
|
|
675
|
+
|
|
676
|
+
How:
|
|
677
|
+
The client sends a request to register an event (e.g., onDtcStatusChange)
|
|
678
|
+
and specifies the response that the server should send when that event
|
|
679
|
+
occurs.
|
|
680
|
+
|
|
681
|
+
Note:
|
|
682
|
+
This service is not fully implemented in this simulator.
|
|
683
|
+
"""
|
|
684
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
685
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
686
|
+
self.supported_subfunctions = [
|
|
687
|
+
self.uds_server.SFID.DO_NOT_STORE_EVENT,
|
|
688
|
+
self.uds_server.SFID.STORE_EVENT,
|
|
689
|
+
self.uds_server.SFID.STOP_RESPONSE_ON_EVENT,
|
|
690
|
+
self.uds_server.SFID.ON_DTC_STATUS_CHANGE,
|
|
691
|
+
self.uds_server.SFID.ON_TIMER_INTERRUPT,
|
|
692
|
+
self.uds_server.SFID.ON_CHANGE_OF_DATA_IDENTIFIER,
|
|
693
|
+
self.uds_server.SFID.REPORT_ACTIVATED_EVENTS,
|
|
694
|
+
self.uds_server.SFID.START_RESPONSE_ON_EVENT,
|
|
695
|
+
self.uds_server.SFID.CLEAR_RESPONSE_ON_EVENT,
|
|
696
|
+
self.uds_server.SFID.ON_COMPARISON_OF_VALUE,
|
|
697
|
+
]
|
|
698
|
+
|
|
699
|
+
def process_request(self, data_stream: list) -> list:
|
|
700
|
+
"""
|
|
701
|
+
Processes a Response On Event request.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
data_stream: The request data stream.
|
|
705
|
+
|
|
706
|
+
Returns:
|
|
707
|
+
A negative response, as this service is not supported.
|
|
708
|
+
"""
|
|
709
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
710
|
+
self.uds_server.SID.ROE, self.uds_server.NRC.SERVICE_NOT_SUPPORTED
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
|
|
714
|
+
class LinkControl:
|
|
715
|
+
"""
|
|
716
|
+
Handles Link Control (0x87) service requests.
|
|
717
|
+
|
|
718
|
+
What:
|
|
719
|
+
This service is used to control the baud rate of the communication
|
|
720
|
+
link between the client and the server.
|
|
721
|
+
|
|
722
|
+
Why:
|
|
723
|
+
It can be used to switch to a higher baud rate for faster data
|
|
724
|
+
transfer, which is particularly useful for time-consuming operations
|
|
725
|
+
like software flashing.
|
|
726
|
+
|
|
727
|
+
How:
|
|
728
|
+
The process typically involves the client first verifying that the
|
|
729
|
+
server can support the new baud rate and then sending a command to
|
|
730
|
+
transition to it.
|
|
731
|
+
|
|
732
|
+
Note:
|
|
733
|
+
This service is not fully implemented in this simulator.
|
|
734
|
+
"""
|
|
735
|
+
def __init__(self, uds_server: 'UdsServer') -> None:
|
|
736
|
+
self.uds_server: 'UdsServer' = uds_server
|
|
737
|
+
self.supported_subfunctions = [
|
|
738
|
+
self.uds_server.SFID.VERIFY_MODE_TRANSITION_WITH_FIXED_PARAMETER,
|
|
739
|
+
self.uds_server.SFID.VERIFY_MODE_TRANSITION_WITH_SPECIFIC_PARAMETER,
|
|
740
|
+
self.uds_server.SFID.TRANSITION_MODE,
|
|
741
|
+
]
|
|
742
|
+
|
|
743
|
+
def process_request(self, data_stream: list) -> list:
|
|
744
|
+
"""
|
|
745
|
+
Processes a Link Control request.
|
|
746
|
+
|
|
747
|
+
Args:
|
|
748
|
+
data_stream: The request data stream.
|
|
749
|
+
|
|
750
|
+
Returns:
|
|
751
|
+
A negative response, as this service is not supported.
|
|
752
|
+
"""
|
|
753
|
+
return self.uds_server.negative_response.report_negative_response(
|
|
754
|
+
self.uds_server.SID.LC, self.uds_server.NRC.SERVICE_NOT_SUPPORTED
|
|
755
|
+
)
|