py-uds-demo 25.0.0__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.

Potentially problematic release.


This version of py-uds-demo might be problematic. Click here for more details.

@@ -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
+ )