def __init__(self, arb_id_request, arb_id_response, bus=None): MockEcu.__init__(self, bus) self.ARBITRATION_ID_REQUEST = arb_id_request self.ARBITRATION_ID_RESPONSE = arb_id_response self.iso_tp = IsoTp(arb_id_request=self.ARBITRATION_ID_REQUEST, arb_id_response=self.ARBITRATION_ID_RESPONSE, bus=self.bus)
def send_key(arb_id_request, arb_id_response, level, key, timeout): """ Sends an Send key message to 'arb_id_request'. Returns the first response received from 'arb_id_response' within 'timeout' seconds or None otherwise. :param arb_id_request: arbitration ID for requests :param arb_id_response: arbitration ID for responses :param level: vehicle manufacturer specific access level to send key for :param key: key to transmit :param timeout: seconds to wait for response before timeout, or None for default UDS timeout :type arb_id_request: int :type arb_id_response: int :type level: int :type key: [int] :type timeout: float or None :return: list of response byte values on success, None otherwise :rtype [int] or None """ # Sanity checks if not Services.SecurityAccess.RequestSeedOrSendKey().is_valid_send_key_level(level): raise ValueError("Invalid send key level") if isinstance(timeout, float) and timeout < 0.0: raise ValueError("timeout value ({0}) cannot be negative".format(timeout)) with IsoTp(arb_id_request=arb_id_request, arb_id_response=arb_id_response) as tp: # Setup filter for incoming messages tp.set_filter_single_arbitration_id(arb_id_response) with Iso14229_1(tp) as uds: # Set timeout if timeout is not None: uds.P3_CLIENT = timeout response = uds.security_access_send_key(level=level, key=key) return response
def ecu_reset(arb_id_request, arb_id_response, reset_type, timeout): """Sends an ECU Reset message to 'arb_id_request'. Returns the first response received from 'arb_id_response' within 'timeout' seconds or None otherwise. :param arb_id_request: arbitration ID for requests :param arb_id_response: arbitration ID for responses :param reset_type: value corresponding to a reset type :param timeout: seconds to wait for response before timeout, or None for default UDS timeout :type arb_id_request: int :type arb_id_response int :type reset_type: int :type timeout: float or None :return: list of response byte values on success, None otherwise :rtype [int] or None """ # Sanity checks if not BYTE_MIN <= reset_type <= BYTE_MAX: raise ValueError("reset type must be within interval 0x{0:02x}-0x{1:02x}".format(BYTE_MIN, BYTE_MAX)) if isinstance(timeout, float) and timeout < 0.0: raise ValueError("timeout value ({0}) cannot be negative".format(timeout)) with IsoTp(arb_id_request=arb_id_request, arb_id_response=arb_id_response) as tp: # Setup filter for incoming messages tp.set_filter_single_arbitration_id(arb_id_response) with Iso14229_1(tp) as uds: # Set timeout if timeout is not None: uds.P3_CLIENT = timeout response = uds.ecu_reset(reset_type=reset_type) return response
def service_discovery(arb_id_request, arb_id_response, timeout, min_id=BYTE_MIN, max_id=BYTE_MAX, print_results=True): """Scans for supported UDS services on the specified arbitration ID. Returns a list of found service IDs. :param arb_id_request: arbitration ID for requests :param arb_id_response: arbitration ID for responses :param timeout: delay between each request sent :param min_id: first service ID to scan :param max_id: last service ID to scan :param print_results: whether progress should be printed to stdout :type arb_id_request: int :type arb_id_response: int :type timeout: float :type min_id: int :type max_id: int :type print_results: bool :return: list of supported service IDs :rtype [int] """ found_services = [] with IsoTp(arb_id_request=arb_id_request, arb_id_response=arb_id_response) as tp: # Setup filter for incoming messages tp.set_filter_single_arbitration_id(arb_id_response) # Send requests try: for service_id in range(min_id, max_id + 1): tp.send_request([service_id]) if print_results: print("\rProbing service 0x{0:02x} ({0}/{1}): found {2}". format(service_id, max_id, len(found_services)), end="") stdout.flush() # Get response msg = tp.bus.recv(timeout) if msg is None: # No response received continue # Parse response if len(msg.data) > 3: # Since service ID is included in the response, mapping # is correct even if response is delayed service_id = msg.data[2] status = msg.data[3] if (status != NegativeResponseCodes.SERVICE_NOT_SUPPORTED): # Any other response than "service not supported" # counts found_services.append(service_id) if print_results: print("\nDone!\n") except KeyboardInterrupt: if print_results: print("\nInterrupted by user!\n") return found_services
def extended_session(arb_id_request, arb_id_response, session_type): with IsoTp(arb_id_request=arb_id_request, arb_id_response=arb_id_response) as tp: # Setup filter for incoming messages tp.set_filter_single_arbitration_id(arb_id_response) with Iso14229_1(tp) as uds: response = uds.diagnostic_session_control(session_type) return response
def tester_present(arb_id_request, delay, duration, suppress_positive_response): """Sends TesterPresent messages to 'arb_id_request'. Stops automatically after 'duration' seconds or runs forever if this is None. :param arb_id_request: arbitration ID for requests :param delay: seconds between each request :param duration: seconds before automatically stopping, or None to continue forever :param suppress_positive_response: whether positive responses should be suppressed :type arb_id_request: int :type delay: float :type duration: float or None :type suppress_positive_response: bool """ # SPR simply tells the recipient not to send a positive response to # each TesterPresent message if suppress_positive_response: sub_function = 0x80 else: sub_function = 0x00 # Calculate end timestamp if the TesterPresent should automatically # stop after a given duration auto_stop = duration is not None end_time = None if auto_stop: end_time = (datetime.datetime.now() + datetime.timedelta(seconds=duration)) service_id = Services.TesterPresent.service_id message_data = [service_id, sub_function] print("Sending TesterPresent to arbitration ID {0} (0x{0:02x})".format( arb_id_request)) print("\nPress Ctrl+C to stop\n") with IsoTp(arb_id_request, None) as can_wrap: counter = 1 while True: can_wrap.send_request(message_data) print("\rCounter:", counter, end="") stdout.flush() time.sleep(delay) counter += 1 if auto_stop and datetime.datetime.now() >= end_time: break
def __init__(self, arb_id_request, arb_id_response, bus=None): MockEcu.__init__(self, bus) self.ARBITRATION_ID_ISO_14229_REQUEST = arb_id_request self.ARBITRATION_ID_ISO_14229_RESPONSE = arb_id_response # Set CAN filter to only listen to incoming requests on the correct arbitration ID arbitration_id_filter = [{ "can_id": arb_id_request, "can_mask": 0x1fffffff }] self.bus.set_filters(arbitration_id_filter) # Setup ISO-TP using the filtered bus self.iso_tp = IsoTp( arb_id_request=self.ARBITRATION_ID_ISO_14229_REQUEST, arb_id_response=self.ARBITRATION_ID_ISO_14229_RESPONSE, bus=self.bus) # Setup diagnostics on top of ISO-TP self.diagnostics = Iso14229_1(tp=self.iso_tp)
def __uds_discovery_wrapper(args): """Wrapper used to initiate a UDS discovery scan""" min_id = args.min max_id = args.max blacklist = args.blacklist auto_blacklist_duration = args.autoblacklist delay = args.delay verify = not args.skipverify print_results = True try: arb_id_pairs = uds_discovery(min_id, max_id, blacklist, auto_blacklist_duration, delay, verify, print_results) if len(arb_id_pairs) == 0: # No UDS discovered print("\nDiagnostics service could not be found.") else: # Print result table print("\nIdentified diagnostics:\n") table_line = "+------------+------------+" print(table_line) print("| CLIENT ID | SERVER ID |") print(table_line) for (client_id, server_id) in arb_id_pairs: print("| 0x{0:08x} | 0x{1:08x} |".format(client_id, server_id)) with IsoTp(arb_id_request=client_id, arb_id_response=server_id) as tp: # Setup filter for incoming messages tp.set_filter_single_arbitration_id(server_id) with Iso14229_1(tp) as uds: resp = uds.clear_all_dtcs() print(resp) print(table_line) except ValueError as e: print("Discovery failed: {0}".format(e))
def uds_discovery(min_id, max_id, blacklist_args, auto_blacklist_duration, delay, verify, print_results=True): """Scans for diagnostics support by brute forcing session control messages to different arbitration IDs. Returns a list of all (client_arb_id, server_arb_id) pairs found. :param min_id: start arbitration ID value :param max_id: end arbitration ID value :param blacklist_args: blacklist for arbitration ID values :param auto_blacklist_duration: seconds to scan for interfering arbitration IDs to blacklist automatically :param delay: delay between each message :param verify: whether found arbitration IDs should be verified :param print_results: whether results should be printed to stdout :type min_id: int :type max_id: int :type blacklist_args: [int] :type auto_blacklist_duration: float :type delay: float :type verify: bool :type print_results: bool :return: list of (client_arbitration_id, server_arbitration_id) pairs :rtype [(int, int)] """ # Set defaults if min_id is None: min_id = ARBITRATION_ID_MIN if max_id is None: if min_id <= ARBITRATION_ID_MAX: max_id = ARBITRATION_ID_MAX else: # If min_id is extended, use an extended default max_id as well max_id = ARBITRATION_ID_MAX_EXTENDED if auto_blacklist_duration is None: auto_blacklist_duration = 0 if blacklist_args is None: blacklist_args = [] # Sanity checks if max_id < min_id: raise ValueError("max_id must not be smaller than min_id - got min:0x{0:x}, max:0x{1:x}".format(min_id, max_id)) if auto_blacklist_duration < 0: raise ValueError("auto_blacklist_duration must not be smaller than 0, got {0}'".format(auto_blacklist_duration)) service_id = Services.DiagnosticSessionControl.service_id sub_function = Services.DiagnosticSessionControl.DiagnosticSessionType.DEFAULT_SESSION session_control_data = [service_id, sub_function] valid_session_control_responses = [0x50, 0x7F] def is_valid_response(message): return len(message.data) >= 2 and message.data[1] in valid_session_control_responses found_arbitration_ids = [] with IsoTp(None, None) as tp: blacklist = set(blacklist_args) # Perform automatic blacklist scan if auto_blacklist_duration > 0: auto_blacklist_arb_ids = auto_blacklist(tp.bus, auto_blacklist_duration, is_valid_response, print_results) blacklist |= auto_blacklist_arb_ids # Prepare session control frame session_control_frames = tp.get_frames_from_message(session_control_data) send_arbitration_id = min_id - 1 while send_arbitration_id < max_id: send_arbitration_id += 1 if print_results: print("\rSending Diagnostic Session Control to 0x{0:04x}".format(send_arbitration_id), end="") stdout.flush() # Send Diagnostic Session Control tp.transmit(session_control_frames, send_arbitration_id, None) end_time = time.time() + delay # Listen for response while time.time() < end_time: msg = tp.bus.recv(0) if msg is None: # No response received continue if msg.arbitration_id in blacklist: # Ignore blacklisted arbitration IDs continue if is_valid_response(msg): # Valid response if verify: # Verification - backtrack the latest IDs and verify that the same response is received verified = False # Set filter to only receive messages for the arbitration ID being verified tp.set_filter_single_arbitration_id(msg.arbitration_id) if print_results: print("\n Verifying potential response from 0x{0:04x}".format(send_arbitration_id)) verify_id_range = range(send_arbitration_id, send_arbitration_id - VERIFICATION_BACKTRACK, -1) for verification_arbitration_id in verify_id_range: if print_results: print(" Resending 0x{0:0x}... ".format(verification_arbitration_id), end=" ") tp.transmit(session_control_frames, verification_arbitration_id, None) # Give some extra time for verification, in case of slow responses verification_end_time = time.time() + delay + VERIFICATION_EXTRA_DELAY while time.time() < verification_end_time: verification_msg = tp.bus.recv(0) if verification_msg is None: continue if is_valid_response(verification_msg): # Verified verified = True # Update send ID - if server responds slowly, the initial value may be faulty. # It also ensures we resume searching on the next arbitration ID after the actual # match, rather than the one after the last potential match (which could lead to # false negatives if multiple servers listen to adjacent arbitration IDs and respond # slowly) send_arbitration_id = verification_arbitration_id break if print_results: # Print result if verified: print("Success") else: print("No response") if verified: # Verification succeeded - stop checking break # Remove filter after verification tp.clear_filters() if not verified: # Verification failed - move on if print_results: print(" False match - skipping") continue if print_results: if not verify: # Blank line needed print() print("Found diagnostics server listening at 0x{0:04x}, response at 0x{1:04x}".format( send_arbitration_id, msg.arbitration_id)) # Add found arbitration ID pair found_arb_id_pair = (send_arbitration_id, msg.arbitration_id) found_arbitration_ids.append(found_arb_id_pair) if print_results: print() return found_arbitration_ids
class MockEcuIsoTp(MockEcu): """ISO-15765-2 (ISO-TP) mock ECU handler""" MOCK_SINGLE_FRAME_REQUEST = [0x01, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF] MOCK_SINGLE_FRAME_RESPONSE = list(range(0, 0x07)) MOCK_MULTI_FRAME_TWO_MESSAGES_REQUEST = [ 0xC0, 0xFF, 0xEE, 0x00, 0x02, 0x00, 0x00 ] MOCK_MULTI_FRAME_TWO_MESSAGES_RESPONSE = list(range(0, 0x0D)) MOCK_MULTI_FRAME_LONG_MESSAGE_REQUEST = [ 0x02, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F ] MOCK_MULTI_FRAME_LONG_MESSAGE_RESPONSE = list(range(0, 34)) def __init__(self, arb_id_request, arb_id_response, bus=None): MockEcu.__init__(self, bus) self.ARBITRATION_ID_REQUEST = arb_id_request self.ARBITRATION_ID_RESPONSE = arb_id_response self.iso_tp = IsoTp(arb_id_request=self.ARBITRATION_ID_REQUEST, arb_id_response=self.ARBITRATION_ID_RESPONSE, bus=self.bus) def __enter__(self): """ Run server when entering a "with" statement. :return: self """ self.start_server() return self def __exit__(self, exc_type, exc_val, exc_tb): """ Cleanup when leaving a "with" statement. :param exc_type: :param exc_val: :param exc_tb: :return: None """ MockEcu.__exit__(self, None, None, None) self.stop_server() def start_server(self): """ Starts a server process, listening for and responding to incoming ISO-TP messages. Since the server runs in a separate process, this function is non-blocking. :return: None """ self.message_process = multiprocessing.Process( target=self._serve_forever) self.message_process.start() def stop_server(self): """ Stops the server process. :return: None """ MockEcu.__exit__(self, None, None, None) if isinstance(self.message_process, multiprocessing.Process): self.message_process.terminate() self.message_process.join() else: print("stop_server: No server was running") def _serve_forever(self): """ Listens for incoming ISO-TP messages and responds to them. This function is blocking. :return: None """ while True: msg = self.iso_tp.indication() if msg is not None: # ISO-TP message received self.message_handler(msg) def message_handler(self, data): """ Logic for responding to incoming messages :param data: list of data bytes in incoming message :return: None """ # Simulate a small delay before responding time.sleep(self.DELAY_BEFORE_RESPONSE) if data == self.MOCK_SINGLE_FRAME_REQUEST: self.iso_tp.send_response(self.MOCK_SINGLE_FRAME_RESPONSE) elif data == self.MOCK_MULTI_FRAME_TWO_MESSAGES_REQUEST: self.iso_tp.send_response( self.MOCK_MULTI_FRAME_TWO_MESSAGES_RESPONSE) elif data == self.MOCK_MULTI_FRAME_LONG_MESSAGE_REQUEST: self.iso_tp.send_response( self.MOCK_MULTI_FRAME_LONG_MESSAGE_RESPONSE) else: print("Unmapped message in {0}.message_handler:\n {1}".format( self.__class__.__name__, data))