예제 #1
0
    def __parse_fcp(self, fcp):
        # see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF,
        # DF or ADF
        from pytlv.TLV import TLV
        tlvparser = TLV([
            '82', '83', '84', 'a5', '8a', '8b', '8c', '80', 'ab', 'c6', '81',
            '88'
        ])

        # pytlv is case sensitive!
        fcp = fcp.lower()

        if fcp[0:2] != '62':
            raise ValueError(
                'Tag of the FCP template does not match, expected 62 but got %s'
                % fcp[0:2])

        # Unfortunately the spec is not very clear if the FCP length is
        # coded as one or two byte vale, so we have to try it out by
        # checking if the length of the remaining TLV string matches
        # what we get in the length field.
        # See also ETSI TS 102 221, chapter 11.1.1.3.0 Base coding.
        exp_tlv_len = int(fcp[2:4], 16)
        if len(fcp[4:]) // 2 == exp_tlv_len:
            skip = 4
        else:
            exp_tlv_len = int(fcp[2:6], 16)
            if len(fcp[4:]) // 2 == exp_tlv_len:
                skip = 6

        # Skip FCP tag and length
        tlv = fcp[skip:]
        return tlvparser.parse(tlv)
예제 #2
0
    def __init__(self, IsoMsg=None, IsoSpec=None):

        self.strict = False

        self.__Bitmap = {}
        self.__FieldData = {}
        self.__iso = b''
        self.tlv = TLV()

        self.__IsoSpec = IsoSpec if IsoSpec != None else spec.IsoSpec1987ASCII(
        )

        if IsoMsg:
            if isinstance(IsoMsg, bytes) == False:
                raise TypeError('Expected bytes for iso message')

            self.__iso = IsoMsg
            self.ParseIso()
예제 #3
0
    def __get_len_from_tlv(self, fcp):
        # Note: This has been taken from http://git.osmocom.org/pysim/tree/pySim/commands.py,
        # but pySim uses ascii-hex strings for its internal data representation. We use
        # regular lists with integers, so we must convert to an ascii-hex string first:
        fcp = ''.join('{:02x}'.format(x) for x in fcp)

        # see also: ETSI TS 102 221, chapter 11.1.1.3.1 Response for MF,
        # DF or ADF
        from pytlv.TLV import TLV
        tlvparser = TLV([
            '82', '83', '84', 'a5', '8a', '8b', '8c', '80', 'ab', 'c6', '81',
            '88'
        ])

        # pytlv is case sensitive!
        fcp = fcp.lower()

        if fcp[0:2] != '62':
            raise ValueError(
                'Tag of the FCP template does not match, expected 62 but got %s'
                % fcp[0:2])

        # Unfortunately the spec is not very clear if the FCP length is
        # coded as one or two byte vale, so we have to try it out by
        # checking if the length of the remaining TLV string matches
        # what we get in the length field.
        # See also ETSI TS 102 221, chapter 11.1.1.3.0 Base coding.
        exp_tlv_len = int(fcp[2:4], 16)
        if len(fcp[4:]) / 2 == exp_tlv_len:
            skip = 4
        else:
            exp_tlv_len = int(fcp[2:6], 16)
            if len(fcp[4:]) / 2 == exp_tlv_len:
                skip = 6

        # Skip FCP tag and length
        tlv = fcp[skip:]
        tlv_parsed = tlvparser.parse(tlv)

        if '80' in tlv_parsed:
            return int(tlv_parsed['80'], 16)
        else:
            return 0
예제 #4
0
    def __init__(self, type, card, term, icc_trxn=None, timeout=None):
        """
        """
        self.TLV = TLV()

        self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
        self.card = card
        self.term = term
        self.type = type.lower()
        self.description = ''
        self.account_from = '00'
        self.account_to = '00'
        self.expected_response_code = '000'
        self.expected_response_action = None
        self.currency = None
        self.PIN = None
        self._set_icc_trxn(icc_trxn)
        self.field48 = {}
        self.timeout = int(timeout) if timeout else 0

        if self.type in ['logon', 'echo']:
            """
            """
            self.IsoMessage.MTI("0800")

            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(24, 801)

        elif self.type == 'key change':
            """
            """
            self.IsoMessage.MTI("0800")

            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(24, 811)

        elif self.type == 'balance':
            """
            """
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(13, get_MMDD())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'purchase':
            """
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'virtual purchase':
            """
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)

        elif self.type == 'refund':
            """
            Refund
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type in ['pin change', 'pin change reversal']:
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            if self.type == 'pin change':
                self.IsoMessage.MTI("0100")
            elif self.type == 'pin change reversal':
                self.IsoMessage.MTI("0400")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'cash':
            """
            Cash
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'dcc check':
            """
            DCC Availability check
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0300")
            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 301)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'create virtual card':
            """
            Create Virtual Card
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        else:
            print('Unknown transaction type: {}'.format(type))
            return None

        # Common message fields:
        self.set_processing_code()
        self.IsoMessage.FieldData(7, get_seconds_since_epoch())
        self.IsoMessage.FieldData(11, get_stan())
        self.IsoMessage.FieldData(41, self.term.get_terminal_id())
        self.IsoMessage.FieldData(42, self.term.get_merchant_id())
        self.IsoMessage.FieldData(49, self.term.get_currency_code())

        if self.type in ['purchase', 'balance', 'cash'] and self.icc_trxn:
            self.IsoMessage.FieldData(55, self.build_emv_data())

        self.rebuild()
예제 #5
0
class Transaction():
    def __init__(self, type, card, term, icc_trxn=None, timeout=None):
        """
        """
        self.TLV = TLV()

        self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
        self.card = card
        self.term = term
        self.type = type.lower()
        self.description = ''
        self.account_from = '00'
        self.account_to = '00'
        self.expected_response_code = '000'
        self.expected_response_action = None
        self.currency = None
        self.PIN = None
        self._set_icc_trxn(icc_trxn)
        self.field48 = {}
        self.timeout = int(timeout) if timeout else 0

        if self.type in ['logon', 'echo']:
            """
            """
            self.IsoMessage.MTI("0800")

            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(24, 801)

        elif self.type == 'key change':
            """
            """
            self.IsoMessage.MTI("0800")

            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(24, 811)

        elif self.type == 'balance':
            """
            """
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(13, get_MMDD())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'purchase':
            """
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'virtual purchase':
            """
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)

        elif self.type == 'refund':
            """
            Refund
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type in ['pin change', 'pin change reversal']:
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            if self.type == 'pin change':
                self.IsoMessage.MTI("0100")
            elif self.type == 'pin change reversal':
                self.IsoMessage.MTI("0400")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'cash':
            """
            Cash
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'dcc check':
            """
            DCC Availability check
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0300")
            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 301)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        elif self.type == 'create virtual card':
            """
            Create Virtual Card
            """
            self.IsoMessage = ISO8583(IsoSpec=IsoSpec1987BPC())
            self.IsoMessage.MTI("0100")

            self.IsoMessage.FieldData(2, self.card.get_card_number())
            self.IsoMessage.FieldData(12, get_datetime_with_year())
            self.IsoMessage.FieldData(22, self.term.get_pos_entry_mode())
            self.IsoMessage.FieldData(24, 100)
            self.IsoMessage.FieldData(25, 0)
            self.IsoMessage.FieldData(35, self.card.get_track2())

        else:
            print('Unknown transaction type: {}'.format(type))
            return None

        # Common message fields:
        self.set_processing_code()
        self.IsoMessage.FieldData(7, get_seconds_since_epoch())
        self.IsoMessage.FieldData(11, get_stan())
        self.IsoMessage.FieldData(41, self.term.get_terminal_id())
        self.IsoMessage.FieldData(42, self.term.get_merchant_id())
        self.IsoMessage.FieldData(49, self.term.get_currency_code())

        if self.type in ['purchase', 'balance', 'cash'] and self.icc_trxn:
            self.IsoMessage.FieldData(55, self.build_emv_data())

        self.rebuild()

    def get_data(self):
        """
        """
        return struct.pack("!H", len(self.data)) + self.data

    def rebuild(self):
        """
        Rebuild IsoMessage (e.g. after field data change)
        """
        self.data = self.IsoMessage.BuildIso()

    def trace(self, header=None):
        """
        """
        self.IsoMessage.Print(header=header)

    def set_description(self, description):
        """
        """
        self.description = description

    def get_description(self):
        """
        Get transaction description (for logging purposes)
        """
        if self.card:
            card_description = self.card.get_description()
        else:
            card_description = 'Cardless'

        if card_description:
            card_description += ' | '

        return card_description + self.description if self.description else card_description + self.type + ' ' + str(
            self.IsoMessage.FieldData(11))

    def set_PIN(self, PIN):
        """
        """
        if PIN:
            self.PIN = PIN
            encrypted_pinblock = self.term.get_encrypted_pin(
                PIN, self.card.get_card_number())
            if encrypted_pinblock:
                self.IsoMessage.FieldData(52, encrypted_pinblock)
                self.rebuild()

    def get_PIN(self):
        """
        """
        return self.PIN

    def set_STAN(self, STAN):
        """
        """
        if STAN and int(STAN) < 1000000 and int(STAN) > 0:
            self.IsoMessage.FieldData(11, int(STAN))
            self.rebuild()
        else:
            raise ValueError('Invalid STAN')

    def get_timeout(self):
        """
        """
        return self.timeout

    def set_amount(self, amount):
        """
        Set transaction amount
        """
        if amount:
            try:
                self.IsoMessage.FieldData(4, int(amount))
            except ValueError:
                self.IsoMessage.FieldData(4, 0)
            self.rebuild()

    def set_expected_code(self, expected_response_code):
        """
        Expected response code of the transaction
        """
        self.expected_response_code = expected_response_code

    def set_expected_action(self, expected_response_action):
        """
        Expected outcome of the transaction ('APPROVED' or 'DECLINED')
        """
        if expected_response_action.upper() not in [
                'APPROVED', 'APPROVE', 'DECLINED', 'DECLINE'
        ]:
            return False

        self.expected_response_action = expected_response_action.upper()
        return True

    def is_response_expected(self, actual_response_code):
        """
        """
        if self.expected_response_action in ['APPROVED', 'APPROVE']:
            return True if int(actual_response_code) == 0 else False

        elif self.expected_response_action in ['DECLINED', 'DECLINE']:
            return True if int(actual_response_code) != 0 else False

        else:
            # no response action available, compare the response codes:
            if self.expected_response_code:
                return True if self.expected_response_code == actual_response_code else False
            else:
                # neither response action, nor response code are set
                return None

        return True

    def _get_app_interchange_profile(self):
        """
        Contains the data objects (with tags and lengths) returned by the ICC in response to a command
        """
        #return '9A039505'
        return '0000'

    def build_emv_data(self):
        """
        TODO:
        95    TVR
        82    app_int_prof 
        """
        emv_data = ''
        emv_data += self.TLV.build({'82': self._get_app_interchange_profile()})
        emv_data += self.TLV.build({'9A': get_date()})
        emv_data += self.TLV.build({'95': self.term.get_tvr()})
        emv_data += self.TLV.build(
            {'9F10': self.card.get_iss_application_data()})
        emv_data += self.TLV.build(
            {'9F26': self.card.get_application_cryptogram()})
        emv_data += self.TLV.build(
            {'9F36': self.card.get_transaction_counter()})
        emv_data += self.TLV.build({'9F37': self.term.get_unpredno()})
        emv_data += self.TLV.build({'9F1A': self.term.get_country_code()})
        return emv_data

    def _set_icc_trxn(self, icc_trxn):
        """
        """
        try:
            if icc_trxn == None and self.card.get_service_code()[0] in [
                    '2', '6'
            ]:
                self.icc_trxn = True
            elif icc_trxn == None and self.card.get_service_code()[0] not in [
                    '2', '6'
            ]:
                self.icc_trxn = False
            elif icc_trxn.lower() == 'true':
                self.icc_trxn = True
            else:
                self.icc_trxn = False
        except AttributeError:
            # self.card is None for cardless transactions
            pass

    def set_currency(self, currency_id):
        """
        Set transaction currency code from given currency id, e.g. set 840 from 'USD'
        """
        try:
            self.currency = currency_codes[currency_id]
            self.IsoMessage.FieldData(49, self.currency)
            self.rebuild()
        except KeyError:
            self.currency = None

    def get_currency(self):
        """
        Currency getter
        """
        return self.currency

    def set_field48_tags(self, tag, tag_value):
        """
        """
        self.field48[tag] = tag_value
        self.build_field48()

    def build_field48(self):
        """
        """
        field48_data = ''
        for tag in self.field48:
            field48_data += tag + str(len(
                self.field48[tag])).zfill(3) + self.field48[tag]

        self.IsoMessage.FieldData(48, field48_data)
        self.rebuild()

    def set_field54(self, field_data):
        """
        """
        self.IsoMessage.FieldData(54, field_data)
        self.rebuild()

    def set_processing_code(self):
        """
        """
        if self.type in ['purchase', 'dcc check']:
            trxn_type_code = '00'
        elif self.type in ['cash']:
            trxn_type_code = '12'
        elif self.type in ['virtual purchase']:
            trxn_type_code = '15'
        elif self.type in ['refund']:
            trxn_type_code = '20'
        elif self.type in ['balance']:
            trxn_type_code = '31'
        elif self.type in ['pin change', 'pin change reversal']:
            trxn_type_code = '76'
        elif self.type in ['create virtual card']:
            trxn_type_code = '87'
        elif self.type in ['logon', 'echo', 'key change']:
            trxn_type_code = '99'
        else:
            trxn_type_code = None

        if trxn_type_code:
            self.processing_code = int(trxn_type_code + self.account_from +
                                       self.account_to)
        else:
            self.processing_code = None

        self.IsoMessage.FieldData(3, self.processing_code)
        self.rebuild()

    def set_account_from(self, account_type):
        """
        """
        self.account_from = account_type
        self.set_processing_code()

    def set_account_to(self, account_type):
        """
        """
        self.account_to = account_type
        self.set_processing_code()
예제 #6
0
class ISO8583:

    ValidContentTypes = ('a', 'n', 's', 'an', 'as', 'ns', 'ans', 'b', 'z')

    def __init__(self, IsoMsg=None, IsoSpec=None):

        self.strict = False

        self.__Bitmap = {}
        self.__FieldData = {}
        self.__iso = b''
        self.tlv = TLV()

        self.__IsoSpec = IsoSpec if IsoSpec != None else spec.IsoSpec1987ASCII(
        )

        if IsoMsg:
            if isinstance(IsoMsg, bytes) == False:
                raise TypeError('Expected bytes for iso message')

            self.__iso = IsoMsg
            self.ParseIso()

    def Strict(self, Value):
        if Value != True and Value != False:
            raise ValueError
        self.strict = Value

    def SetIsoContent(self, IsoMsg):
        if isinstance(IsoMsg, bytes) == False:
            raise TypeError('Expected bytes for iso message')
        self.__iso = IsoMsg
        self.ParseIso()

    def ParseMTI(self, p):
        DataType = self.__IsoSpec.DataType('MTI')

        if DataType == DT.BCD:
            self.__MTI = Bcd2Str(self.__iso[p:p + 2])
            p += 2
        elif DataType == DT.ASCII:
            self.__MTI = self.__iso[p:p + 4].decode('latin')
            p += 4

        try:  # MTI should only contain numbers
            int(self.__MTI)
        except:
            raise ParseError('Invalid MTI: [{0}]'.format(self.__MTI))

        if self.strict == True:
            if self.__MTI[1] == '0':
                raise ParseError(
                    'Invalid MTI: Invalid Message type [{0}]'.format(
                        self.__MTI))

            if int(self.__MTI[3]) > 5:
                raise ParseError(
                    'Invalid MTI: Invalid Message origin [{0}]'.format(
                        self.__MTI))

        return p

    def ParseBitmap(self, p):
        DataType = self.__IsoSpec.DataType(1)

        if DataType == DT.BIN:
            Primary = self.__iso[p:p + 8]
            p += 8
        elif DataType == DT.ASCII:
            Primary = binascii.unhexlify(self.__iso[p:p + 16])
            p += 16

        IntPrimary = struct.unpack_from('!Q', Primary)[0]

        for i in range(1, 65):
            self.__Bitmap[i] = (IntPrimary >> (64 - i)) & 0x1

        if self.__Bitmap[1] == 1:
            if DataType == DT.BIN:
                Secondary = self.__iso[p:p + 8]
                p += 8
            elif DataType == DT.ASCII:
                Secondary = binascii.unhexlify(self.__iso[p:p + 16])
                p += 16

            IntSecondary = struct.unpack_from('!Q', Secondary)[0]

            for i in range(1, 65):
                self.__Bitmap[i + 64] = (IntSecondary >> (64 - i)) & 0x1

        return p

    def ParseField(self, field, p):

        try:
            DataType = self.__IsoSpec.DataType(field)
            LenType = self.__IsoSpec.LengthType(field)
            ContentType = self.__IsoSpec.ContentType(field)
            MaxLength = self.__IsoSpec.MaxLength(field)
        except:
            raise SpecError(
                'Cannot parse F{0}: Incomplete field specification'.format(
                    field))

        try:
            if DataType == DT.ASCII and ContentType == 'b':
                MaxLength *= 2

            if LenType == LT.FIXED:
                Len = MaxLength
            elif LenType == LT.LVAR:
                pass
            elif LenType == LT.LLVAR:
                LenDataType = self.__IsoSpec.LengthDataType(field)
                if LenDataType == DT.ASCII:
                    Len = int(self.__iso[p:p + 2])
                    p += 2
                elif LenDataType == DT.BCD:
                    Len = Bcd2Int(self.__iso[p:p + 1])
                    p += 1
            elif LenType == LT.LLLVAR:
                LenDataType = self.__IsoSpec.LengthDataType(field)
                if LenDataType == DT.ASCII:
                    Len = int(self.__iso[p:p + 3])
                    p += 3
                elif LenDataType == DT.BCD:
                    Len = Bcd2Int(self.__iso[p:p + 2])
                    p += 2
        except ValueError:
            raise ParseError('Cannot parse F{0}: Invalid length'.format(field))

        if Len > MaxLength:
            raise ParseError(
                'F{0} is larger than maximum length ({1}>{2})'.format(
                    field, Len, MaxLength))

        # In case of zero length, don't try to parse the field itself, just continue
        if Len == 0:
            return p

        try:
            if DataType == DT.ASCII:
                if ContentType == 'n':
                    self.__FieldData[field] = int(self.__iso[p:p + (Len)])
                else:
                    self.__FieldData[field] = self.__iso[p:p +
                                                         (Len)].decode('latin')
                p += Len
            elif DataType == DT.BCD:
                if Len % 2 == 1:
                    Len += 1
                if ContentType == 'n':
                    self.__FieldData[field] = Bcd2Int(self.__iso[p:p +
                                                                 (Len // 2)])
                elif ContentType == 'z':
                    self.__FieldData[field] = binascii.hexlify(
                        self.__iso[p:p + (Len // 2)]).decode('latin').upper()
                p += Len // 2
            elif DataType == DT.BIN:
                self.__FieldData[field] = binascii.hexlify(
                    self.__iso[p:p + (Len)]).decode('latin').upper()
                p += Len
        except:
            raise ParseError('Cannot parse F{0}'.format(field))

        if ContentType == 'z':
            self.__FieldData[field] = self.__FieldData[field].replace(
                'D', '=')  # in track2, replace d with =
            self.__FieldData[field] = self.__FieldData[field].replace(
                'F', '')  # in track2, remove trailing f

        return p

    def ParseIso(self):
        p = 0
        p = self.ParseMTI(p)
        p = self.ParseBitmap(p)

        for field in sorted(self.__Bitmap):
            # field 1 is parsed by the bitmap function
            if field != 1 and self.Field(field) == 1:
                p = self.ParseField(field, p)

    def BuildMTI(self):
        if self.__IsoSpec.DataType('MTI') == DT.BCD:
            self.__iso += Str2Bcd(self.__MTI)
        elif self.__IsoSpec.DataType('MTI') == DT.ASCII:
            self.__iso += self.__MTI.encode('latin')

    def BuildBitmap(self):
        DataType = self.__IsoSpec.DataType(1)

        # check if we need a secondary bitmap
        for i in self.__Bitmap.keys():
            if i > 64:
                self.__Bitmap[1] = 1
                break

        IntPrimary = 0
        for i in range(1, 65):
            if i in self.__Bitmap.keys():
                IntPrimary |= (self.__Bitmap[i] & 0x1) << (64 - i)

        Primary = struct.pack('!Q', IntPrimary)

        if DataType == DT.BIN:
            self.__iso += Primary
        elif DataType == DT.ASCII:
            self.__iso += binascii.hexlify(Primary)

        # Add secondary bitmap if applicable
        if 1 in self.__Bitmap.keys() and self.__Bitmap[1] == 1:

            IntSecondary = 0
            for i in range(65, 129):
                if i in self.__Bitmap.keys():
                    IntSecondary |= (self.__Bitmap[i] & 0x1) << (128 - i)

            Secondary = struct.pack('!Q', IntSecondary)

            if DataType == DT.BIN:
                self.__iso += Secondary
            elif DataType == DT.ASCII:
                self.__iso += binascii.hexlify(Secondary)

    def BuildField(self, field):
        try:
            DataType = self.__IsoSpec.DataType(field)
            LenType = self.__IsoSpec.LengthType(field)
            ContentType = self.__IsoSpec.ContentType(field)
            MaxLength = self.__IsoSpec.MaxLength(field)
        except:
            raise SpecError(
                'Cannot parse F{0}: Incomplete field specification'.format(
                    field))

        data = ''
        if LenType == LT.FIXED:
            Len = MaxLength

            if ContentType == 'n':
                formatter = '{{0:0{0}d}}'.format(Len)
            elif 'a' in ContentType or 'n' in ContentType or 's' in ContentType:
                formatter = '{{0: >{0}}}'.format(Len)
            else:
                formatter = '{0}'

            data = formatter.format(self.__FieldData[field])

        else:
            LenDataType = self.__IsoSpec.LengthDataType(field)

            try:
                data = '{0}'.format(self.__FieldData[field])
            except KeyError:
                data = ''
            Len = len(data)
            if DataType == DT.BIN:
                Len //= 2

            if Len > MaxLength:
                raise BuildError(
                    'Cannot Build F{0}: Field Length larger than specification'
                    .format(field))

            if LenType == LT.LVAR:
                LenData = '{0:01d}'.format(Len)

            elif LenType == LT.LLVAR:
                LenData = '{0:02d}'.format(Len)

            elif LenType == LT.LLLVAR:
                LenData = '{0:03d}'.format(Len)

            if LenDataType == DT.ASCII:
                self.__iso += LenData.encode('latin')
            elif LenDataType == DT.BCD:
                self.__iso += Str2Bcd(LenData)
            elif LenDataType == DT.BIN:
                self.__iso += binascii.unhexlify(LenData)

        if ContentType == 'z':
            data = data.replace('=', 'D')
            #if(len(data) % 2 == 1):
            #    data = data + 'F'

        if DataType == DT.ASCII:
            self.__iso += data.encode('latin')
        elif DataType == DT.BCD:
            self.__iso += Str2Bcd(data)
        elif DataType == DT.BIN:
            self.__iso += binascii.unhexlify(self.__FieldData[field])

    def BuildIso(self):
        self.__iso = b''
        self.BuildMTI()
        self.BuildBitmap()

        for field in sorted(self.__Bitmap):
            if field != 1 and self.Field(field) == 1:
                self.BuildField(field)

        return self.__iso

    def RemoveField(self, field):
        '''
        '''
        try:
            self.__FieldData[field] = None
            del (self.__Bitmap[field])
        except KeyError:
            pass

    def Field(self, field, Value=None):
        '''
        Add field to bitmap
        '''
        if Value == None:
            try:
                return self.__Bitmap[field]
            except KeyError:
                return None
        elif Value == 1 or Value == 0:
            self.__Bitmap[field] = Value
        else:
            raise ValueError

    def SetBitmap(self, fields):
        '''
        Set the message bitmap with the value from fields array
        '''
        for field in fields:
            self.Field(field, Value=1)

    def FieldData(self, field, Value=None):
        '''
        Add field data
        '''
        if Value == None:
            try:
                return self.__FieldData[field]
            except KeyError:
                return None
        else:
            if len(str(Value)) > self.__IsoSpec.MaxLength(field):
                raise ValueError(
                    'Value length larger than field maximum ({0})'.format(
                        self.__IsoSpec.MaxLength(field)))

            self.Field(field, Value=1)
            self.__FieldData[field] = Value

    def Bitmap(self):
        return self.__Bitmap

    def MTI(self, MTI=None):
        if MTI == None:
            return self.__MTI
        else:
            try:  # MTI should only contain numbers
                int(MTI)
            except:
                raise ValueError(
                    'Invalid MTI [{0}]: MTI must contain only numbers'.format(
                        MTI))

            if self.strict == True:
                if MTI[1] == '0':
                    raise ValueError(
                        'Invalid MTI [{0}]: Invalid Message type'.format(MTI))

                if int(MTI[3]) > 5:
                    raise ValueError(
                        'Invalid MTI [{0}]: Invalid Message origin'.format(
                            MTI))

            self.__MTI = MTI

    def get_MTI(self):
        '''
        '''
        return self.__MTI

    def Description(self, field):
        return self.__IsoSpec.Description(field)

    def DataType(self, field, DataType=None):
        return self.__IsoSpec.DataType(field, DataType)

    def ContentType(self, field, ContentType=None):
        return self.__IsoSpec.ContentType(field, ContentType)

    def PrintMessage(self):
        self.Print()

    def Print(self, header=None):
        if header:
            print('\t{} at {}:'.format(header, get_timestamp()))
        else:
            print('\tParsed message:')

        try:
            print('\tMTI:    [{0}]'.format(self.__MTI))
        except AttributeError:
            pass

        bitmapLine = '\tFields: [ '
        for i in sorted(self.__Bitmap.keys()):
            if i == 1:
                continue
            if self.__Bitmap[i] == 1:
                bitmapLine += str(i) + ' '
        bitmapLine += ']'
        print(bitmapLine)

        for i in sorted(self.__Bitmap.keys()):
            if i == 1:
                continue
            if self.__Bitmap[i] == 1:
                try:
                    FieldData = self.__FieldData[i]
                except KeyError:
                    FieldData = ''

                if self.ContentType(i) == 'n' and self.__IsoSpec.LengthType(
                        i) == LT.FIXED:
                    FieldData = str(FieldData).zfill(
                        self.__IsoSpec.MaxLength(i))
                if i == 39:
                    print('\t\t{0:>3d} - {1: <41} [{2}]\t\t\t[{3}]'.format(
                        i, self.__IsoSpec.Description(i), FieldData,
                        self.__IsoSpec.RespCodeDescription(FieldData)))
                elif i == 55:
                    print('\t\t{0:>3d} - {1: <41} [{2}]'.format(
                        i, self.__IsoSpec.Description(i), FieldData))
                    print(
                        self.tlv.dump(self.tlv.parse(FieldData),
                                      left_indent='\t\t',
                                      desc_column_width=38))
                else:
                    print('\t\t{0:>3d} - {1: <41} [{2}]'.format(
                        i, self.__IsoSpec.Description(i), FieldData))

        print('\n')