Esempio n. 1
0
 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)
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
 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)
Esempio n. 8
0
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))
Esempio n. 9
0
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
Esempio n. 10
0
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))