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)
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 __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
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()
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()
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')