diff --git a/ISO8583/ISO8583.py b/ISO8583/ISO8583.py index d38d021..98fb339 100644 --- a/ISO8583/ISO8583.py +++ b/ISO8583/ISO8583.py @@ -18,7 +18,7 @@ """ __author__ = 'Igor Vitorio Custodio ' -__version__ = '1.4' +__version__ = '1.5' __licence__ = 'GPL V3' import sys @@ -30,6 +30,8 @@ import binascii import ebcdic import time +import re + class ISO8583: """Main Class to work with ISO8583 packages. @@ -86,14 +88,14 @@ class ISO8583: # ISO8583 constants _BITS_VALUE_TYPE = {} # Every _BITS_VALUE_TYPE has: - # _BITS_VALUE_TYPE[N] = [ X, Y, Z, W, K, L ] + # _BITS_VALUE_TYPE[N] = [X, Y, Z, V, W, K, L] # N = bitnumber # X = smallStr representation of the bit meaning # Y = large str representation - # Z = type of the bit (B, N, A, AN, ANS, LL, LLL, LLLLLL) + # Z = type of the bit (B, N, A, AN, ANS, A_or_N, LL, LLL, LLLLLL) # V = format of indicator length indicator LL, LLL, etc (-, A[scii], B[CD]) # W = size of the information that N need to has - # K = type of values a, an, n, ans, b + # K = type of values a, an, n, ans, b, a_or_n # L = format of the data (A[scii], E[bcdic], P[acked]) _BITS_VALUE_TYPE[1] = ['BME', 'Bit Map Extended', 'B', '-', 16, 'b', 'A'] _BITS_VALUE_TYPE[2] = ['2', 'Primary account number (PAN)', 'LL', 'A', 19, 'n', 'A'] @@ -136,7 +138,7 @@ class ISO8583: _BITS_VALUE_TYPE[33] = [ '33', 'Forwarding institution identification code', 'LL', 'A', 11, 'n', 'A'] _BITS_VALUE_TYPE[34] = ['34', 'Primary Account Number, extended', 'LL', 'A', 28, 'n', 'A'] - _BITS_VALUE_TYPE[35] = ['35', 'Track 2 data', 'LL', 'A', 37, 'n', 'A'] + _BITS_VALUE_TYPE[35] = ['35', 'Track 2 data', 'LL', 'A', 37, 'ans', 'A'] _BITS_VALUE_TYPE[36] = ['36', 'Track 3 data', 'LLL', 'A', 104, 'n', 'A'] _BITS_VALUE_TYPE[37] = ['37', 'Retrieval reference number', 'N', '-', 12, 'an', 'A'] _BITS_VALUE_TYPE[38] = ['38', 'Approval code', 'N', '-', 6, 'an', 'A'] @@ -153,10 +155,10 @@ class ISO8583: _BITS_VALUE_TYPE[46] = ['46', 'Amounts fees', 'LLL', 'A', 999, 'ans', 'A'] _BITS_VALUE_TYPE[47] = ['47', 'Additional data national', 'LLL', 'A', 999, 'ans', 'A'] _BITS_VALUE_TYPE[48] = ['48', 'Additional data private', 'LLL', 'A', 999, 'ans', 'A'] - _BITS_VALUE_TYPE[49] = ['49', 'Currency code, transaction', 'AN', '-', 3, 'an', 'A'] - _BITS_VALUE_TYPE[50] = ['50', 'Currency code, settlement', 'AN', '-', 3, 'an', 'A'] + _BITS_VALUE_TYPE[49] = ['49', 'Currency code, transaction', 'A_or_N', '-', 3, 'a_or_n', 'A'] + _BITS_VALUE_TYPE[50] = ['50', 'Currency code, settlement', 'A_or_N', '-', 3, 'a_or_n', 'A'] _BITS_VALUE_TYPE[51] = [ - '51', 'Currency code, cardholder billing', 'AN', '-', 3, 'an', 'A'] + '51', 'Currency code, cardholder billing', 'A_or_N', '-', 3, 'a_or_n', 'A'] _BITS_VALUE_TYPE[52] = [ '52', 'Personal identification number (PIN) data', 'B', '-', 16, 'b', 'A'] _BITS_VALUE_TYPE[53] = [ @@ -265,7 +267,7 @@ class ISO8583: _BITS_VALUE_TYPE[124] = ['124', 'Info Text', 'LLL', 'A', 255, 'ans', 'A'] _BITS_VALUE_TYPE[125] = [ '125', 'Network management information', 'LL', 'A', 50, 'ans', 'A'] - _BITS_VALUE_TYPE[126] = ['126', 'Issuer trace id', 'LL', 'A', 6, 'ans', 'A'] + _BITS_VALUE_TYPE[126] = ['126', 'Issuer trace id', 'LLL', 'A', 999, 'ans', 'A'] _BITS_VALUE_TYPE[127] = ['127', 'Reserved for private use', 'LLL', 'A', 999, 'ans', 'A'] _BITS_VALUE_TYPE[128] = ['128', 'Message authentication code (MAC) field', 'B', '-', 16, 'b', 'A'] @@ -382,7 +384,7 @@ def getBitFormat(self,bit): ################################################################################################ ################################################################################################ - # Set the header + # Set the header def setHdr(self, hdr): """Method that sets the optional header byte string """ @@ -413,7 +415,7 @@ def setHdrlen(self, hdrlen): ################################################################################################ ################################################################################################ - # Get the header + # Get the header def getHdr(self): """Method that gets the optional header byte string """ @@ -607,6 +609,9 @@ def setBit(self, bit, value): if self.getBitType(bit) == 'B': self.__setBitTypeB(bit, value) + if self.getBitType(bit) == 'A_or_N': + self.__setBitTypeA_or_N(bit, value) + # Continuation bit? if bit > 64: # need to set bit 1 of first "bit" in bitmap @@ -812,7 +817,7 @@ def __getBitsFromBitmap(self): ################################################################################################ # Method that receive a ISO8583 ASCII package in the network form and parse it. - def __formatValue(self,bit,value): + def __formatValue(self, bit, value): """Method that formats a value to the appropriate data for the bit or LL or LLL bits @param: bit -> bit to be setted @param: value -> value to be setted @@ -864,6 +869,9 @@ def __setBitTypeLL(self, bit, value): data = self.__formatValue(bit, value) lenform = self.getBitLenForm(bit) + self.__check_bit_type_validity(bit, value) + self.__check_bit_data_length(bit, value, size) + if lenform == 'A': self.BITMAP_VALUES[bit] = size.zfill(2).encode() + data elif lenform == 'E': @@ -873,6 +881,7 @@ def __setBitTypeLL(self, bit, value): else: self.BITMAP_VALUES[bit] = self.__IntToLLBCD(len(value)) + data + ################################################################################################ ################################################################################################ @@ -901,6 +910,9 @@ def __setBitTypeLLL(self, bit, value): data = self.__formatValue(bit, value) lenform = self.getBitLenForm(bit) + self.__check_bit_type_validity(bit, value) + self.__check_bit_data_length(bit, value, size) + if lenform == 'A': self.BITMAP_VALUES[bit] = size.zfill(3).encode() + data elif lenform == 'E': @@ -938,6 +950,9 @@ def __setBitTypeLLLLLL(self, bit, value): data = self.__formatValue(bit, value) lenform = self.getBitLenForm(bit) + self.__check_bit_type_validity(bit, value) + self.__check_bit_data_length(bit, value, size) + if lenform == 'A': self.BITMAP_VALUES[bit] = size.zfill(6).encode() + data elif lenform == 'E': @@ -969,7 +984,44 @@ def __setBitTypeN(self, bit, value): raise ValueTooLarge('Error: value up to size! Bit[%s] of type %s limit size = %s' % ( bit, self.getBitType(bit), self.getBitLimit(bit))) - #self.__checkBitTypeValidity(bit, value) + self.__check_bit_type_validity(bit, value) + + data_form = self.getBitFormat(bit) + + if data_form == "A": + self.BITMAP_VALUES[bit] = value.zfill(self.getBitLimit(bit)).encode() + elif data_form == "E": + self.BITMAP_VALUES[bit] = value.zfill(self.getBitLimit(bit)).encode('cp1148') + else: # Packed data - make sure that it's left zero-filled to the correct length- a multiple of 2 + unpacked_len = self.__getUnpackedLen(self.getBitLimit(bit)) + self.BITMAP_VALUES[bit] = binascii.unhexlify(value.zfill(unpacked_len)) + + ################################################################################################ + + ################################################################################################ + # Set of type A_or_N, + def __setBitTypeA_or_N(self, bit, value): + """Method that set a bit with value in form A_or_N + It complete the size of the bit with a default value + Example: pack.setBit(49,'20') -> Bit 49 is a A_or_N type, so this bit, + in ASCII form need to has size = 3 (ISO especification) so the + value 20 size = 2 need to receive "1" more number. + In this case, will be "0" in the left. In the package, + the bit will be sent like '020' + @param: bit -> bit to be setted + @param: value -> value to be setted + @raise: ValueToLarge Exception + It's a internal method, so don't call! + """ + + value = "%s" % value + + if len(value) > self.getBitLimit(bit): + value = value[0:self.getBitLimit(bit)] + raise ValueTooLarge('Error: value up to size! Bit[%s] of type %s limit size = %s' % ( + bit, self.getBitType(bit), self.getBitLimit(bit))) + + self.__check_bit_type_validity(bit, value) data_form = self.getBitFormat(bit) @@ -977,7 +1029,7 @@ def __setBitTypeN(self, bit, value): self.BITMAP_VALUES[bit] = value.zfill(self.getBitLimit(bit)).encode() elif data_form == "E": self.BITMAP_VALUES[bit] = value.zfill(self.getBitLimit(bit)).encode('cp1148') - else: # Packed data - make sure that it's left zero-filled to the correct length- a multiple of 2 + else: # Packed data - make sure that it's left zero-filled to the correct length- a multiple of 2 unpacked_len = self.__getUnpackedLen(self.getBitLimit(bit)) self.BITMAP_VALUES[bit] = binascii.unhexlify(value.zfill(unpacked_len)) @@ -1003,7 +1055,7 @@ def __setBitTypeA(self, bit, value): raise ValueTooLarge('Error: value up to size! Bit[%s] of type %s limit size = %s' % ( bit, self.getBitType(bit), self.getBitLimit(bit))) - #self.__checkBitTypeValidity(bit, value) + self.__check_bit_type_validity(bit, value) data_form = self.getBitFormat(bit) @@ -1034,7 +1086,7 @@ def __setBitTypeAN(self, bit, value): raise ValueTooLarge('Error: value up to size! Bit[%s] of type %s limit size = %s' % ( bit, self.getBitType(bit), self.getBitLimit(bit))) - #self.__checkBitTypeValidity(bit, value) + self.__check_bit_type_validity(bit, value) data_form = self.getBitFormat(bit) @@ -1097,7 +1149,7 @@ def __setBitTypeANS(self, bit, value): raise ValueTooLarge('Error: value up to size! Bit[%s] of type %s limit size = %s' % ( bit, self.getBitType(bit), self.getBitLimit(bit))) - #self.__checkBitTypeValidity(bit, value) + self.__check_bit_type_validity(bit, value) data_form = self.getBitFormat(bit) @@ -1320,43 +1372,88 @@ def getValuesArray(self): ################################################################################################ - def __raiseValueTypeError(self, bit): + def __raiseValueTypeError(self, bit, value=None): """ Raise a type error exception @param: bit -> bit that caused the error @raises: InvalidValueType -> exception with message according to type error """ - raise InvalidValueType( - 'Error: value of type %s has invalid type' % (self.getBitType(bit)) - ) + if value: + msg = ( + "Error: Bit {bit} of type {type} and" + " value {value} has invalid type".format( + bit=bit, type=self.getBitType(bit), value=value)) + else: + msg = "Error: value of type {type} has invalid type".format( + type=self.getBitType(bit)) + + raise InvalidValueType(msg) ################################################################################################ ################################################################################################ - def __checkBitTypeValidity(self, bit, value): + def __check_bit_type_validity(self, bit, value): """ Verify that a bit's value has the correct type - @param: bit -> bit to be validated - @param: value -> bit's value as a string - @raises: InvalidValueType -> exception with message according to - type error + :param int bit: bit to be validated + :param str or byte value: bit's value + :raises InvalidValueType: exception with message according to + type error """ bitType = self.getBitValueType(bit) - - if bitType == 'a': - if not all(x.isspace() or x.isalpha() for x in value): - self.__raiseValueTypeError(bit) - elif bitType == 'n': - if not value.isdecimal(): - self.__raiseValueTypeError(bit) - elif bitType == 'an': - if not all(x.isspace() or x.isalnum() for x in value): - self.__raiseValueTypeError(bit) + bitFormat = self.getBitFormat(bit) + + if bitFormat == 'A': + if type(value) == bytes: + value = value.decode('ascii') + + # ASCII format, able to decode and use str methods to validate + if bitType == 'a': + if not all(x.isspace() or x.isalpha() for x in value): + self.__raiseValueTypeError(bit, value) + elif bitType == 'n': + if not value.isdecimal(): + self.__raiseValueTypeError(bit, value) + elif bitType == 'an': + if not all(x.isspace() or x.isalnum() for x in value): + self.__raiseValueTypeError(bit, value) + elif bitType == 'as': + if not all(not x.isdecimal() for x in value): + self.__raiseValueTypeError(bit, value) + elif bitType == 'ns': + if not all(not x.isalpha() for x in value): + self.__raiseValueTypeError(bit, value) + elif bitType == 'a_or_n': + # It has to be either alpha or numeric. + if not ( (all(x.isspace() or x.isalpha() for x in value)) or + (value.isdecimal()) ): + self.__raiseValueTypeError(bit, value) # No exceptions raised, return return True + def __check_bit_data_length(self, bit, value, size): + """ + Verify the data size of the data in the LL values, the data should + match the size given by the first bits. + :param int bit: The bit to validate + :param str value: The value of the bit + :param int size: The size of the field to set + :raises ValueTooLarge: when the value does not match the size + """ + size = int(size) + if len(value) != size: + raise ValueTooLarge( + 'Error: Bit {bit} has a header size of {size} ' + 'but a data size of {data_size}'.format( + bit=bit, + size=size, + data_size=len(value))) + + return True + + ################################################################################################ ################################################################################################ @@ -1365,6 +1462,8 @@ def __getBitFromStr(self, strWithoutMtiBitmap): """Method that receive a string (ASCII) without MTI and Bitmaps (first and second), understand it and remove the bits values @param: str -> with all bits presents whithout MTI and bitmap It's a internal method, so don't call! + @raises: InvalidValueType -> exception with message according to + type error """ if self.DEBUG is True: @@ -1399,22 +1498,33 @@ def __getBitFromStr(self, strWithoutMtiBitmap): print('Size of the message in LL = %s' % valueSize) if valueSize > self.getBitLimit(cont): - print('This bit is larger than the specification.') - # raise ValueTooLarge("This bit is larger than the specification!") + print( + "The bit's {} value is larger than " + "the specification.".format(cont)) + raise ValueTooLarge( + "The bit's {} value is larger than " + "the specification.".format(cont)) if self.getBitFormat(cont) == 'P': modvalueSize = self.__getPackedLen(valueSize) else: # ASCII and EBCDIC have the same length modvalueSize = valueSize - self.BITMAP_VALUES[cont] = strWithoutMtiBitmap[offset:offset+lenoffset] + strWithoutMtiBitmap[ - offset+lenoffset:offset+lenoffset+ modvalueSize] + data_size = strWithoutMtiBitmap[offset:offset+lenoffset] + data_value = strWithoutMtiBitmap[ + offset + lenoffset:offset + lenoffset + + modvalueSize] + bit_value = data_size + data_value + self.__check_bit_type_validity(cont, data_value) + self.__check_bit_data_length(cont, data_value, data_size) + + self.BITMAP_VALUES[cont] = bit_value if self.DEBUG is True: print('\tSetting bit %s value %s' % (cont, self.BITMAP_VALUES[cont])) - offset += modvalueSize + lenoffset + offset += modvalueSize + lenoffset elif bitType == 'LLL': if lenform == 'A': @@ -1434,16 +1544,26 @@ def __getBitFromStr(self, strWithoutMtiBitmap): print('Size of the message in LLL = %s' % valueSize) if valueSize > self.getBitLimit(cont): + print( + "The bit's {} value is larger than " + "the specification.".format(cont)) raise ValueTooLarge( - "This bit is larger than the specification!") + "The bit's {} value is larger than " + "the specification.".format(cont)) if self.getBitFormat(cont) == 'P': modvalueSize = self.__getPackedLen(valueSize) else: modvalueSize = valueSize - self.BITMAP_VALUES[cont] = strWithoutMtiBitmap[offset:offset+lenoffset] + strWithoutMtiBitmap[ - offset+lenoffset:offset+lenoffset+modvalueSize] + data_size = strWithoutMtiBitmap[offset:offset + lenoffset] + data_value = strWithoutMtiBitmap[ + offset + lenoffset:offset + lenoffset + modvalueSize] + bit_value = data_size + data_value + self.__check_bit_type_validity(cont, data_value) + self.__check_bit_data_length(cont, data_value, data_size) + + self.BITMAP_VALUES[cont] = bit_value if self.DEBUG is True: print('\tSetting bit %s value %s' % @@ -1469,16 +1589,26 @@ def __getBitFromStr(self, strWithoutMtiBitmap): print('Size of the message in LLLLLL = %s' % valueSize) if valueSize > self.getBitLimit(cont): + print( + "The bit's {} value is larger than " + "the specification.".format(cont)) raise ValueTooLarge( - "This bit is larger than the specification!") + "The bit's {} value is larger than " + "the specification.".format(cont)) if self.getBitFormat(cont) == 'P': modvalueSize = self.__getPackedLen(valueSize) else: modvalueSize = valueSize - self.BITMAP_VALUES[cont] = strWithoutMtiBitmap[offset:offset+lenoffset] + strWithoutMtiBitmap[ - offset+lenoffset:offset+lenoffset+modvalueSize] + data_size = strWithoutMtiBitmap[offset:offset + lenoffset] + data_value = strWithoutMtiBitmap[ + offset + lenoffset:offset + lenoffset + modvalueSize] + bit_value = data_size + data_value + self.__check_bit_type_validity(cont, data_value) + self.__check_bit_data_length(cont, data_value, data_size) + + self.BITMAP_VALUES[cont] = bit_value if self.DEBUG is True: print('\tSetting bit %s value %s' % @@ -1494,7 +1624,7 @@ def __getBitFromStr(self, strWithoutMtiBitmap): # offset += valueSize + 4 elif bitType == 'N' or bitType == 'A' or bitType == 'ANS' or \ - bitType == 'B' or bitType == 'AN': + bitType == 'B' or bitType == 'AN' or bitType == 'A_or_N': origvalueSize = self.getBitLimit(cont) @@ -1503,10 +1633,10 @@ def __getBitFromStr(self, strWithoutMtiBitmap): else: modvalueSize = origvalueSize - value = strWithoutMtiBitmap[offset:modvalueSize + offset] + bit_value = strWithoutMtiBitmap[offset:modvalueSize + offset] - #self.__checkBitTypeValidity(cont, value) - self.BITMAP_VALUES[cont] = value + self.__check_bit_type_validity(cont, bit_value) + self.BITMAP_VALUES[cont] = bit_value if self.DEBUG is True: print('\tSetting bit %s value %s' % @@ -1817,6 +1947,37 @@ def getBit(self, bit): else: raise BitNotSet("Bit number %s was not set!" % bit) + + ################################################################################################ + + ################################################################################################ + # Method that returns the value of the bit + def getBitValue(self, bit): + """Return the value of the bit without + @param: bit -> the number of the bit that you want the value + @raise: BitNonexistent Exception, BitNotSet Exception + """ + + # Get the raw bit + value = self.getBit(bit) + return_value = None + + # Get the bit's type + bit_type = self.getBitType(bit) + + if bit_type == 'L': + return_value = value[1:] + elif bit_type == 'LL': + return_value = value[2:] + elif bit_type == 'LLL': + return_value = value[3:] + elif bit_type in ['N', 'B', 'A', 'AN', 'ANS']: + return_value = value + + return return_value + + + ################################################################################################ ################################################################################################ diff --git a/setup.py b/setup.py index d1be970..e7fb26e 100755 --- a/setup.py +++ b/setup.py @@ -10,4 +10,5 @@ url='https://github.com/ducminhgd/python-ISOMessage8583', packages=['ISO8583'], keywords='ISO8583' + install_requires=['ebcdic==1.1.1'] )