def test_severity_behaviour(self):
        dtc = Dtc(1)

        self.assertEqual(dtc.severity.get_byte_as_int(), 0x00)
        dtc.severity.maintenance_only = True
        self.assertEqual(dtc.severity.get_byte_as_int(), 0x20)
        dtc.severity.check_at_next_exit = True
        self.assertEqual(dtc.severity.get_byte_as_int(), 0x60)
        dtc.severity.check_immediately = True
        self.assertEqual(dtc.severity.get_byte_as_int(), 0xE0)

        dtc.severity.set_byte(0x20)
        self.assertEqual(dtc.severity.maintenance_only, True)
        self.assertEqual(dtc.severity.check_at_next_exit, False)
        self.assertEqual(dtc.severity.check_immediately, False)

        dtc.severity.set_byte(0x60)
        self.assertEqual(dtc.severity.maintenance_only, True)
        self.assertEqual(dtc.severity.check_at_next_exit, True)
        self.assertEqual(dtc.severity.check_immediately, False)

        dtc.severity.set_byte(0xE0)
        self.assertEqual(dtc.severity.maintenance_only, True)
        self.assertEqual(dtc.severity.check_at_next_exit, True)
        self.assertEqual(dtc.severity.check_immediately, True)
    def test_init(self):
        dtc = Dtc(0x1234)
        self.assertEqual(dtc.id, 0x1234)
        self.assertEqual(dtc.status.get_byte(), b'\x00')
        self.assertEqual(dtc.status.get_byte_as_int(), 0x00)
        self.assertEqual(dtc.severity.get_byte(), b'\x00')
        self.assertEqual(dtc.severity.get_byte_as_int(), 0x00)

        self.assertEqual(dtc.status.test_failed, False)
        self.assertEqual(dtc.status.test_failed_this_operation_cycle, False)
        self.assertEqual(dtc.status.pending, False)
        self.assertEqual(dtc.status.confirmed, False)
        self.assertEqual(dtc.status.test_not_completed_since_last_clear, False)
        self.assertEqual(dtc.status.test_failed_since_last_clear, False)
        self.assertEqual(dtc.status.test_not_completed_this_operation_cycle,
                         False)
        self.assertEqual(dtc.status.warning_indicator_requested, False)

        with self.assertRaises(TypeError):
            Dtc()
Example #3
0
def read_dtcs(
    flash_info: constants.FlashInfo,
    interface="CAN",
    callback=None,
    interface_path=None,
    status_mask=None,
):
    if status_mask is None:
        # Try to filter to reasonable failures only.
        status_mask = Dtc.Status(
            test_failed=True,
            test_failed_this_operation_cycle=True,
            pending=False,
            confirmed=True,
            test_not_completed_since_last_clear=False,
            test_failed_since_last_clear=True,
            test_not_completed_this_operation_cycle=False,
            warning_indicator_requested=True,
        )
    conn = connection_setup(
        interface=interface,
        rxid=flash_info.control_module_identifier.rxid,
        txid=flash_info.control_module_identifier.txid,
        interface_path=interface_path,
    )
    with Client(conn, request_timeout=5,
                config=configs.default_client_config) as client:
        if callback:
            callback(
                flasher_step="READING",
                flasher_status="Connected",
                flasher_progress=50,
            )
        client.change_session(services.DiagnosticSessionControl.Session.
                              extendedDiagnosticSession)
        response = client.get_dtc_by_status_mask(status_mask.get_byte_as_int())
        if callback:
            callback(
                flasher_step="DONE",
                flasher_status="Read " + str(len(response.service_data.dtcs)) +
                " DTCs",
                flasher_progress=100,
            )
        return dtc_handler.dtcs_to_human(response.service_data.dtcs)
 def test_str_repr(self):
     dtc = Dtc(0x123456)
     dtc.status.pending = True
     str(dtc)
     dtc.__repr__()
 def test_set_severity_with_byte_no_error(self):
     dtc = Dtc(1)
     for i in range(255):
         dtc.severity.set_byte(i)
    def test_status_behaviour(self):
        dtc = Dtc(1)

        self.assertEqual(dtc.status.get_byte(), b'\x00')
        dtc.status.test_failed = True
        self.assertEqual(dtc.status.get_byte(), b'\x01')
        dtc.status.test_failed_this_operation_cycle = True
        self.assertEqual(dtc.status.get_byte(), b'\x03')
        dtc.status.pending = True
        self.assertEqual(dtc.status.get_byte(), b'\x07')
        dtc.status.confirmed = True
        self.assertEqual(dtc.status.get_byte(), b'\x0F')
        dtc.status.test_not_completed_since_last_clear = True
        self.assertEqual(dtc.status.get_byte(), b'\x1F')
        dtc.status.test_failed_since_last_clear = True
        self.assertEqual(dtc.status.get_byte(), b'\x3F')
        dtc.status.test_not_completed_this_operation_cycle = True
        self.assertEqual(dtc.status.get_byte(), b'\x7F')
        dtc.status.warning_indicator_requested = True
        self.assertEqual(dtc.status.get_byte(), b'\xFF')

        dtc.status.set_byte(0x01)
        self.assertEqual(dtc.status.test_failed, True)
        self.assertEqual(dtc.status.test_failed_this_operation_cycle, False)
        self.assertEqual(dtc.status.pending, False)
        self.assertEqual(dtc.status.confirmed, False)
        self.assertEqual(dtc.status.test_not_completed_since_last_clear, False)
        self.assertEqual(dtc.status.test_failed_since_last_clear, False)
        self.assertEqual(dtc.status.test_not_completed_this_operation_cycle,
                         False)
        self.assertEqual(dtc.status.warning_indicator_requested, False)

        dtc.status.set_byte(0x03)
        self.assertEqual(dtc.status.test_failed, True)
        self.assertEqual(dtc.status.test_failed_this_operation_cycle, True)
        self.assertEqual(dtc.status.pending, False)
        self.assertEqual(dtc.status.confirmed, False)
        self.assertEqual(dtc.status.test_not_completed_since_last_clear, False)
        self.assertEqual(dtc.status.test_failed_since_last_clear, False)
        self.assertEqual(dtc.status.test_not_completed_this_operation_cycle,
                         False)
        self.assertEqual(dtc.status.warning_indicator_requested, False)

        dtc.status.set_byte(0x07)
        self.assertEqual(dtc.status.test_failed, True)
        self.assertEqual(dtc.status.test_failed_this_operation_cycle, True)
        self.assertEqual(dtc.status.pending, True)
        self.assertEqual(dtc.status.confirmed, False)
        self.assertEqual(dtc.status.test_not_completed_since_last_clear, False)
        self.assertEqual(dtc.status.test_failed_since_last_clear, False)
        self.assertEqual(dtc.status.test_not_completed_this_operation_cycle,
                         False)
        self.assertEqual(dtc.status.warning_indicator_requested, False)

        dtc.status.set_byte(0x0F)
        self.assertEqual(dtc.status.test_failed, True)
        self.assertEqual(dtc.status.test_failed_this_operation_cycle, True)
        self.assertEqual(dtc.status.pending, True)
        self.assertEqual(dtc.status.confirmed, True)
        self.assertEqual(dtc.status.test_not_completed_since_last_clear, False)
        self.assertEqual(dtc.status.test_failed_since_last_clear, False)
        self.assertEqual(dtc.status.test_not_completed_this_operation_cycle,
                         False)
        self.assertEqual(dtc.status.warning_indicator_requested, False)

        dtc.status.set_byte(0x1F)
        self.assertEqual(dtc.status.test_failed, True)
        self.assertEqual(dtc.status.test_failed_this_operation_cycle, True)
        self.assertEqual(dtc.status.pending, True)
        self.assertEqual(dtc.status.confirmed, True)
        self.assertEqual(dtc.status.test_not_completed_since_last_clear, True)
        self.assertEqual(dtc.status.test_failed_since_last_clear, False)
        self.assertEqual(dtc.status.test_not_completed_this_operation_cycle,
                         False)
        self.assertEqual(dtc.status.warning_indicator_requested, False)

        dtc.status.set_byte(0x3F)
        self.assertEqual(dtc.status.test_failed, True)
        self.assertEqual(dtc.status.test_failed_this_operation_cycle, True)
        self.assertEqual(dtc.status.pending, True)
        self.assertEqual(dtc.status.confirmed, True)
        self.assertEqual(dtc.status.test_not_completed_since_last_clear, True)
        self.assertEqual(dtc.status.test_failed_since_last_clear, True)
        self.assertEqual(dtc.status.test_not_completed_this_operation_cycle,
                         False)
        self.assertEqual(dtc.status.warning_indicator_requested, False)

        dtc.status.set_byte(0x7F)
        self.assertEqual(dtc.status.test_failed, True)
        self.assertEqual(dtc.status.test_failed_this_operation_cycle, True)
        self.assertEqual(dtc.status.pending, True)
        self.assertEqual(dtc.status.confirmed, True)
        self.assertEqual(dtc.status.test_not_completed_since_last_clear, True)
        self.assertEqual(dtc.status.test_failed_since_last_clear, True)
        self.assertEqual(dtc.status.test_not_completed_this_operation_cycle,
                         True)
        self.assertEqual(dtc.status.warning_indicator_requested, False)

        dtc.status.set_byte(0xFF)
        self.assertEqual(dtc.status.test_failed, True)
        self.assertEqual(dtc.status.test_failed_this_operation_cycle, True)
        self.assertEqual(dtc.status.pending, True)
        self.assertEqual(dtc.status.confirmed, True)
        self.assertEqual(dtc.status.test_not_completed_since_last_clear, True)
        self.assertEqual(dtc.status.test_failed_since_last_clear, True)
        self.assertEqual(dtc.status.test_not_completed_this_operation_cycle,
                         True)
        self.assertEqual(dtc.status.warning_indicator_requested, True)
 def test_set_status_with_byte_no_error(self):
     dtc = Dtc(1)
     for i in range(255):
         dtc.status.set_byte(i)
Example #8
0
    def interpret_response(cls,
                           response,
                           subfunction,
                           extended_data_size=None,
                           tolerate_zero_padding=True,
                           ignore_all_zero_dtc=True,
                           dtc_snapshot_did_size=2,
                           didconfig=None):
        u"""
        Populates the response ``service_data`` property with an instance of :class:`ReadDTCInformation.ResponseData<udsoncan.services.ReadDTCInformation.ResponseData>`

        :param response: The received response to interpret
        :type response: :ref:`Response<Response>`

        :param subfunction: The service subfunction. Values are defined in :class:`ReadDTCInformation.Subfunction<udsoncan.services.ReadDTCInformation.Subfunction>`
        :type subfunction: int

        :param extended_data_size: Extended data size to expect. Extended data is implementation specific, therefore, size is not standardized
        :type extended_data_size: int

        :param tolerate_zero_padding: Ignore trailing zeros in the response data avoiding raising false :class:`InvalidResponseException<udsoncan.exceptions.InvalidResponseException>`.
        :type tolerate_zero_padding:  bool

        :param ignore_all_zero_dtc: Discard any DTC entries that have an ID of 0. Avoid reading extra DTCs when using a transport protocol using zero padding.
        :type ignore_all_zero_dtc: bool

        :param dtc_snapshot_did_size: Number of bytes to encode the data identifier number. Other services such as :ref:`ReadDataByIdentifier<ReadDataByIdentifier>` encode DID over 2 bytes.
                UDS standard does not define the size of the snapshot DID, therefore, it must be supplied.
        :type dtc_snapshot_did_size: int

        :param didconfig: Definition of DID codecs. Dictionary mapping a DID (int) to a valid :ref:`DidCodec<DidCodec>` class or pack/unpack string 
        :type didconfig: dict[int] = :ref:`DidCodec<DidCodec>`

        :raises InvalidResponseException: If response length is wrong or does not match DID configuration
        :raises ValueError: If parameters are out of range, missing or wrong types
        :raises ConfigError: If the server returns a snapshot DID not defined in ``didconfig``
        """

        from udsoncan import Dtc, DidCodec
        ServiceHelper.validate_int(subfunction,
                                   min=1,
                                   max=0x15,
                                   name=u'Subfunction')

        # Response grouping for responses that are encoded the same way
        response_subfn_dtc_availability_mask_plus_dtc_record = [
            ReadDTCInformation.Subfunction.reportDTCByStatusMask,
            ReadDTCInformation.Subfunction.reportSupportedDTCs,
            ReadDTCInformation.Subfunction.reportFirstTestFailedDTC,
            ReadDTCInformation.Subfunction.reportFirstConfirmedDTC,
            ReadDTCInformation.Subfunction.reportMostRecentTestFailedDTC,
            ReadDTCInformation.Subfunction.reportMostRecentConfirmedDTC,
            ReadDTCInformation.Subfunction.reportMirrorMemoryDTCByStatusMask,
            ReadDTCInformation.Subfunction.
            reportEmissionsRelatedOBDDTCByStatusMask,
            ReadDTCInformation.Subfunction.reportDTCWithPermanentStatus
        ]

        response_subfn_number_of_dtc = [
            ReadDTCInformation.Subfunction.reportNumberOfDTCByStatusMask,
            ReadDTCInformation.Subfunction.
            reportNumberOfDTCBySeverityMaskRecord,
            ReadDTCInformation.Subfunction.
            reportNumberOfMirrorMemoryDTCByStatusMask,
            ReadDTCInformation.Subfunction.
            reportNumberOfEmissionsRelatedOBDDTCByStatusMask,
        ]

        response_subfn_dtc_availability_mask_plus_dtc_record_with_severity = [
            ReadDTCInformation.Subfunction.reportDTCBySeverityMaskRecord,
            ReadDTCInformation.Subfunction.reportSeverityInformationOfDTC
        ]

        response_subfn_dtc_plus_fault_counter = [
            ReadDTCInformation.Subfunction.reportDTCFaultDetectionCounter
        ]

        response_subfn_dtc_plus_sapshot_record = [
            ReadDTCInformation.Subfunction.reportDTCSnapshotIdentification
        ]

        response_sbfn_dtc_status_snapshots_records = [
            ReadDTCInformation.Subfunction.reportDTCSnapshotRecordByDTCNumber
        ]

        response_sbfn_dtc_status_snapshots_records_record_first = [
            ReadDTCInformation.Subfunction.
            reportDTCSnapshotRecordByRecordNumber
        ]

        response_subfn_mask_record_plus_extdata = [
            ReadDTCInformation.Subfunction.
            reportDTCExtendedDataRecordByDTCNumber, ReadDTCInformation.
            Subfunction.reportMirrorMemoryDTCExtendedDataRecordByDTCNumber
        ]

        response.service_data = cls.ResponseData()  # what will be returned

        if len(response.data) < 1:
            raise InvalidResponseException(
                response,
                u'Response must be at least 1 byte long (echo of subfunction)')

        response.service_data.subfunction_echo = ord(
            response.data[0])  # First byte is subfunction

        # Now for each response group, we have a different decoding algorithm
        if subfunction in response_subfn_dtc_availability_mask_plus_dtc_record + response_subfn_dtc_availability_mask_plus_dtc_record_with_severity:

            if subfunction in response_subfn_dtc_availability_mask_plus_dtc_record:
                dtc_size = 4  # DTC ID (3) + Status (1)
            elif subfunction in response_subfn_dtc_availability_mask_plus_dtc_record_with_severity:
                dtc_size = 6  # DTC ID (3) + Status (1) + Severity (1) + FunctionalUnit (1)

            if len(response.data) < 2:
                raise InvalidResponseException(
                    response,
                    u'Response must be at least 2 byte long (echo of subfunction and DTCStatusAvailabilityMask)'
                )

            response.service_data.status_availability = Dtc.Status.from_byte(
                ord(response.data[1]))

            actual_byte = 2  # Increasing index
            while True:  # Loop until we have read all dtcs
                if len(response.data) <= actual_byte:
                    break  # done

                elif len(response.data) < actual_byte + dtc_size:
                    partial_dtc_length = len(response.data) - actual_byte
                    if tolerate_zero_padding and response.data[
                            actual_byte:] == '\x00' * partial_dtc_length:
                        break
                    else:
                        # We purposely ignore extra byte for subfunction reportSeverityInformationOfDTC as it is supposed to return 0 or 1 DTC.
                        if subfunction != ReadDTCInformation.Subfunction.reportSeverityInformationOfDTC or actual_byte == 2:
                            raise InvalidResponseException(
                                response,
                                u'Incomplete DTC record. Missing %d bytes to response to complete the record'
                                % (dtc_size - partial_dtc_length))

                else:
                    dtc_bytes = response.data[actual_byte:actual_byte +
                                              dtc_size]
                    if dtc_bytes == '\x00' * dtc_size and ignore_all_zero_dtc:
                        pass  # ignore
                    else:
                        if subfunction in response_subfn_dtc_availability_mask_plus_dtc_record:
                            dtc = Dtc(
                                struct.unpack(u'>L',
                                              '\x00' + dtc_bytes[0:3])[0])
                            dtc.status.set_byte(ord(dtc_bytes[3]))
                        elif subfunction in response_subfn_dtc_availability_mask_plus_dtc_record_with_severity:
                            dtc = Dtc(
                                struct.unpack(u'>L',
                                              '\x00' + dtc_bytes[2:5])[0])
                            dtc.severity.set_byte(ord(dtc_bytes[0]))
                            dtc.functional_unit = ord(dtc_bytes[1])
                            dtc.status.set_byte(ord(dtc_bytes[5]))

                        response.service_data.dtcs.append(dtc)
                actual_byte += dtc_size
            response.service_data.dtc_count = len(response.service_data.dtcs)

        # The 2 following subfunction responses have different purposes but their constructions are very similar.
        elif subfunction in response_subfn_dtc_plus_fault_counter + response_subfn_dtc_plus_sapshot_record:
            dtc_size = 4
            if len(response.data) < 1:
                raise InvalidResponseException(
                    response,
                    u'Response must be at least 1 byte long (echo of subfunction)'
                )

            actual_byte = 1  # Increasing index
            dtc_map = dict(
            )  # This map is used to append snapshot to existing DTC.

            while True:  # Loop until we have read all dtcs
                if len(response.data) <= actual_byte:
                    break  # done

                elif len(response.data) < actual_byte + dtc_size:
                    partial_dtc_length = len(response.data) - actual_byte
                    if tolerate_zero_padding and response.data[
                            actual_byte:] == '\x00' * partial_dtc_length:
                        break
                    else:
                        raise InvalidResponseException(
                            response,
                            u'Incomplete DTC record. Missing %d bytes to response to complete the record'
                            % (dtc_size - partial_dtc_length))
                else:
                    dtc_bytes = response.data[actual_byte:actual_byte +
                                              dtc_size]
                    if dtc_bytes == '\x00' * dtc_size and ignore_all_zero_dtc:
                        pass  # ignore
                    else:
                        dtcid = struct.unpack(u'>L',
                                              '\x00' + dtc_bytes[0:3])[0]
                        # We create the DTC or get its reference if already created.
                        dtc_created = False
                        if dtcid in dtc_map and subfunction in response_subfn_dtc_plus_sapshot_record:
                            dtc = dtc_map[dtcid]
                        else:
                            dtc = Dtc(dtcid)
                            dtc_map[dtcid] = dtc
                            dtc_created = True

                        # We either read the DTC fault counter or Snapshot record number.
                        if subfunction in response_subfn_dtc_plus_fault_counter:
                            dtc.fault_counter = ord(dtc_bytes[3])

                        elif subfunction in response_subfn_dtc_plus_sapshot_record:
                            record_number = ord(dtc_bytes[3])

                            if dtc.snapshots is None:
                                dtc.snapshots = []

                            dtc.snapshots.append(record_number)

                        # Adds the DTC to the list.
                        if dtc_created:
                            response.service_data.dtcs.append(dtc)

                actual_byte += dtc_size

            response.service_data.dtc_count = len(response.service_data.dtcs)

        # This group of responses returns a number of DTCs available
        elif subfunction in response_subfn_number_of_dtc:
            if len(response.data) < 5:
                raise InvalidResponseException(
                    response, u'Response must be exactly 5 bytes long ')

            response.service_data.status_availability = Dtc.Status.from_byte(
                ord(response.data[1]))
            response.service_data.dtc_format = ord(response.data[2])
            response.service_data.dtc_count = struct.unpack(
                u'>H', response.data[3:5])[0]

        # This group of responses returns DTC snapshots
        # Responses include a DTC, many snapshot records. For each record, we find many Data Identifiers.
        # We create one Dtc.Snapshot for each DID. That'll be easier to work with.
        # <DTC,RecordNumber1,NumberOfDid_X,DID1,DID2,...DIDX, RecordNumber2,NumberOfDid_Y,DID1,DID2,...DIDY, etc>
        elif subfunction in response_sbfn_dtc_status_snapshots_records:
            if len(response.data) < 5:
                raise InvalidResponseException(
                    response, u'Response must be at least 5 bytes long ')

            dtc = Dtc(struct.unpack(u'>L', '\x00' + response.data[1:4])[0])
            dtc.status.set_byte(ord(response.data[4]))
            actual_byte = 5  # Increasing index

            ServiceHelper.validate_int(dtc_snapshot_did_size,
                                       min=1,
                                       max=8,
                                       name=u'dtc_snapshot_did_size')

            while True:  # Loop until we have read all dtcs
                if len(response.data) <= actual_byte:
                    break  # done

                remaining_data = response.data[actual_byte:]
                if tolerate_zero_padding and remaining_data == '\x00' * len(
                        remaining_data):
                    break

                if len(remaining_data) < 2:
                    raise InvalidResponseException(
                        response,
                        u'Incomplete response from server. Missing "number of identifier" and following data'
                    )

                record_number = ord(remaining_data[0])
                number_of_did = ord(remaining_data[1])
                # Validate record number and number of DID before continuing
                if number_of_did == 0:
                    raise InvalidResponseException(
                        response,
                        u'Server returned snapshot record #%d with no data identifier included'
                        % (record_number))

                if len(remaining_data) < 2 + dtc_snapshot_did_size:
                    raise InvalidResponseException(
                        response,
                        u'Incomplete response from server. Missing DID number and associated data.'
                    )

                actual_byte += 2
                for i in xrange(number_of_did):
                    remaining_data = response.data[actual_byte:]
                    snapshot = Dtc.Snapshot(
                    )  # One snapshot per DID for convenience.
                    snapshot.record_number = record_number

                    # As standard does not specify the length of the DID, we craft it based on a config
                    did = 0
                    for j in xrange(dtc_snapshot_did_size):
                        offset = dtc_snapshot_did_size - 1 - j
                        did |= (ord(remaining_data[offset]) << (8 * j))

                    # Decode the data based on DID number.
                    snapshot.did = did
                    didconfig = ServiceHelper.check_did_config(did, didconfig)
                    codec = DidCodec.from_config(didconfig[did])

                    data_offset = dtc_snapshot_did_size
                    if len(remaining_data[data_offset:]) < len(codec):
                        raise InvalidResponseException(
                            response,
                            u'Incomplete response. Data for DID 0x%04x is only %d bytes while %d bytes is expected'
                            % (did, len(
                                remaining_data[data_offset:]), len(codec)))

                    snapshot.raw_data = remaining_data[
                        data_offset:data_offset + len(codec)]
                    snapshot.data = codec.decode(snapshot.raw_data)

                    dtc.snapshots.append(snapshot)
                    actual_byte += dtc_snapshot_did_size + len(codec)

            response.service_data.dtcs.append(dtc)
            response.service_data.dtc_count = 1

        # This group of responses returns DTC snapshots
        # Responses include a DTC, many snapshots records. For each record, we find many Data Identifiers.
        # We create one Dtc.Snapshot for each DID. That'll be easier to work with.
        # Similar to previous subfunction group, but order of information is changed.

        # <RecordNumber1, DTC1,NumberOfDid_X,DID1,DID2,...DIDX, RecordNumber2,DTC2, NumberOfDid_Y,DID1,DID2,...DIDY, etc>
        elif subfunction in response_sbfn_dtc_status_snapshots_records_record_first:
            ServiceHelper.validate_int(dtc_snapshot_did_size,
                                       min=1,
                                       max=8,
                                       name=u'dtc_snapshot_did_size')

            if len(response.data) < 2:
                raise InvalidResponseException(
                    response,
                    u'Response must be at least 2 bytes long. Subfunction echo + RecordNumber '
                )

            actual_byte = 1  # Increasing index
            while True:  # Loop through response data
                if len(response.data) <= actual_byte:
                    break  # done

                remaining_data = response.data[actual_byte:]
                record_number = ord(remaining_data[0])

                # If empty response but filled with 0, it is considered ok
                if remaining_data == '\x00' * len(
                        remaining_data) and tolerate_zero_padding:
                    break

                # If record number received but no DTC provided (allowed according to standard), we exit.
                if len(remaining_data
                       ) == 1 or tolerate_zero_padding and remaining_data[
                           1:] == '\x00' * len(remaining_data[1:]):
                    break

                if len(remaining_data
                       ) < 5:  # Partial DTC (No DTC at all is checked above)
                    raise InvalidResponseException(
                        response,
                        u'Incomplete response from server. Missing "DTCAndStatusRecord" and following data'
                    )

                if len(remaining_data) < 6:
                    raise InvalidResponseException(
                        response,
                        u'Incomplete response from server. Missing number of data identifier'
                    )

                # DTC decoding
                dtc = Dtc(
                    struct.unpack(u'>L', '\x00' + remaining_data[1:4])[0])
                dtc.status.set_byte(ord(remaining_data[4]))
                number_of_did = ord(remaining_data[5])

                actual_byte += 6
                remaining_data = response.data[actual_byte:]

                if number_of_did == 0:
                    raise InvalidResponseException(
                        response,
                        u'Server returned snapshot record #%d with no data identifier included'
                        % (record_number))

                if len(remaining_data) < dtc_snapshot_did_size:
                    raise InvalidResponseException(
                        response,
                        u'Incomplete response from server. Missing DID and associated data'
                    )

                # We have a DTC and 0 DID, next loop
                if tolerate_zero_padding and remaining_data == '\x00' * len(
                        remaining_data):
                    break

                # For each DID
                for i in xrange(number_of_did):
                    remaining_data = response.data[actual_byte:]
                    snapshot = Dtc.Snapshot(
                    )  # One snapshot epr DID for convenience
                    snapshot.record_number = record_number

                    # As standard does not specify the length of the DID, we craft it based on a config
                    did = 0
                    for j in xrange(dtc_snapshot_did_size):
                        offset = dtc_snapshot_did_size - 1 - j
                        did |= (ord(remaining_data[offset]) << (8 * j))

                    # Decode the data based on DID number.
                    snapshot.did = did
                    didconfig = ServiceHelper.check_did_config(did, didconfig)
                    codec = DidCodec.from_config(didconfig[did])

                    data_offset = dtc_snapshot_did_size
                    if len(remaining_data[data_offset:]) < len(codec):
                        raise InvalidResponseException(
                            response,
                            u'Incomplete response. Data for DID 0x%04x is only %d bytes while %d bytes is expected'
                            % (did, len(
                                remaining_data[data_offset:]), len(codec)))

                    snapshot.raw_data = remaining_data[
                        data_offset:data_offset + len(codec)]
                    snapshot.data = codec.decode(snapshot.raw_data)

                    dtc.snapshots.append(snapshot)

                    actual_byte += dtc_snapshot_did_size + len(codec)

                response.service_data.dtcs.append(dtc)
            response.service_data.dtc_count = len(response.service_data.dtcs)

        # These subfunctions include DTC ExtraData. We give it raw to user.
        elif subfunction in response_subfn_mask_record_plus_extdata:
            cls.assert_extended_data_size(extended_data_size, subfunction)

            if len(response.data) < 5:
                raise InvalidResponseException(
                    response,
                    u'Incomplete response from server. Missing DTCAndStatusRecord'
                )
            # DTC decoding
            dtc = Dtc(struct.unpack(u'>L', '\x00' + response.data[1:4])[0])
            dtc.status.set_byte(ord(response.data[4]))

            actual_byte = 5  # Increasing index
            while actual_byte < len(response.data):  # Loop through data
                remaining_data = response.data[actual_byte:]
                record_number = ord(remaining_data[0])

                if record_number == 0:
                    if remaining_data == '\x00' * len(
                            remaining_data) and tolerate_zero_padding:
                        break
                    else:
                        raise InvalidResponseException(
                            response,
                            u'Extended data record number given by the server is 0 but this value is a reserved value.'
                        )

                actual_byte += 1
                remaining_data = response.data[actual_byte:]

                if len(remaining_data) < extended_data_size:
                    raise InvalidResponseException(
                        response,
                        u'Incomplete response from server. Length of extended data for DTC 0x%06x with record number 0x%02x is %d bytes but smaller than given data_size of %d bytes'
                        % (dtc.id, record_number, len(remaining_data),
                           extended_data_size))

                exdata = Dtc.ExtendedData()
                exdata.record_number = record_number
                exdata.raw_data = remaining_data[0:extended_data_size]

                dtc.extended_data.append(exdata)

                actual_byte += extended_data_size

            response.service_data.dtcs.append(dtc)
            response.service_data.dtc_count = len(response.service_data.dtcs)