From 55abb9dca1fce8fd8b360060952b15add8831937 Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Wed, 25 Sep 2024 09:14:47 -0500 Subject: [PATCH] Deprecate several utility functions In addition, * Add type annotations to utility functions * Update utility function doc strings * Use newer Python constructs to simplify remaining code --- ChangeLog | 2 + .../Examples/scard-api/sample_pinpad.py | 1 - src/smartcard/util/__init__.py | 140 ++++++++---------- test/test_util.py | 12 +- 4 files changed, 73 insertions(+), 82 deletions(-) diff --git a/ChangeLog b/ChangeLog index 27eb142d..2e3e7b2e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -9,6 +9,8 @@ Unreleased changes * Standardize local and CI testing to use tox * Build wheels in CI for all supported Python versions * Build the docs as a part of the test suite + * Begin to add type annotations to the package + * Deprecate the `HexListToBinString`, `BinStringToHexList`, `hl2bs`, and `bs2hl` utility functions 2.1.1 (September 2024) ====================== diff --git a/src/smartcard/Examples/scard-api/sample_pinpad.py b/src/smartcard/Examples/scard-api/sample_pinpad.py index 276e13dd..5a629cee 100755 --- a/src/smartcard/Examples/scard-api/sample_pinpad.py +++ b/src/smartcard/Examples/scard-api/sample_pinpad.py @@ -26,7 +26,6 @@ """ from smartcard.scard import * -from smartcard.util import toASCIIBytes from smartcard.pcsc.PCSCExceptions import * import sys diff --git a/src/smartcard/util/__init__.py b/src/smartcard/util/__init__.py index 33a4c2cb..bd41cd08 100644 --- a/src/smartcard/util/__init__.py +++ b/src/smartcard/util/__init__.py @@ -22,13 +22,16 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ +from __future__ import annotations +import warnings + PACK = 1 HEX = 2 UPPERCASE = 4 COMMA = 8 -def padd(bytelist, length, padding='FF'): +def padd(bytelist: list[int], length: int, padding: str = 'FF'): """ Padds a byte list with a constant byte value (default is x0FF) @param bytelist: the byte list to padd @param length: the total length of the resulting byte list; @@ -45,16 +48,11 @@ def padd(bytelist, length, padding='FF'): [59, 101, 0, 0, 156, 17, 1, 1, 3] """ - newlist = list(bytelist) - if len(newlist) < length: - for index in range(length - len(newlist)): - newlist.append(eval('0x' + padding)) - - return newlist + return bytelist + [int(padding, 16)] * (length - len(bytelist)) -def toASCIIBytes(stringtoconvert): - """Returns a list of ASCII bytes from a string. +def toASCIIBytes(stringtoconvert: str) -> list[int]: + """Convert a string to a list of UTF-8 encoded bytes. @param stringtoconvert: the string to convert into a byte list @@ -66,11 +64,13 @@ def toASCIIBytes(stringtoconvert): [78, 117, 109, 98, 101, 114, 32, 49, 48, 49] """ - return list(map(ord, list(stringtoconvert))) + return list(stringtoconvert.encode("utf-8")) + +def toASCIIString(bytelist: list[int]) -> str: + """Convert a list of integers in the range ``[32, 127]`` to a string. -def toASCIIString(bytelist): - """Returns a string representing a list of ASCII bytes. + Integer values outside the range ``[32, 127]`` are replaced with a period. @param bytelist: list of ASCII bytes to convert into a string @@ -84,20 +84,16 @@ def toASCIIString(bytelist): ". .~." """ - res = [] - for b in bytelist: - if b < 32 or b > 127: - c = '.' - else: - c = chr(b) - res.append(c) - return ''.join(res) + return ''.join( + chr(c) if 32 <= c <= 127 else '.' + for c in bytelist + ) -def toBytes(bytestring): - """Returns a list of bytes from a byte string +def toBytes(bytestring: str) -> list[int]: + """Convert a string of hexadecimal characters to a list of integers. - bytestring: a byte string + @param bytestring: a byte string >>> toBytes("3B 65 00 00 9C 11 01 01 03") [59, 101, 0, 0, 156, 17, 1, 1, 3] @@ -106,10 +102,10 @@ def toBytes(bytestring): >>> toBytes("3B6500 009C1101 0103") [59, 101, 0, 0, 156, 17, 1, 1, 3] """ - packedstring = bytestring.replace(' ', '').replace(' ','').replace('\n', '') + try: - return list(map(lambda x: int(''.join(x), 16), zip(*[iter(packedstring)] * 2))) - except (KeyError, ValueError): + return list(bytes.fromhex(bytestring)) + except ValueError: raise TypeError('not a string representing a list of bytes') @@ -176,7 +172,7 @@ def toBytes(bytestring): } -def toGSM3_38Bytes(stringtoconvert): +def toGSM3_38Bytes(stringtoconvert: str | bytes) -> list[int]: """Returns a list of bytes from a string using GSM 3.38 conversion table. @param stringtoconvert: string to convert @@ -191,19 +187,17 @@ def toGSM3_38Bytes(stringtoconvert): result = [] for char in stringtoconvert: - if ((char >= "%") and (char <= "?")): - result.append(ord(char)) - elif ((char >= "A") and (char <= "Z")): - result.append(ord(char)) - elif ((char >= "a") and (char <= "z")): + if ("%" <= char <= "?") or ("A" <= char <= "Z") or ("a" <= char <= "z"): result.append(ord(char)) else: result.append(__dic_GSM_3_38__[char]) return result -def toHexString(data=[], format=0): - """Returns an hex string representing bytes +def toHexString(data: list[int] | None = None, format: int = 0) -> str: + """Convert a list of integers to a formatted string of hexadecimal. + + Integers larger than 255 will be truncated to two-byte hexadecimal pairs. @param data: a list of bytes to stringify, e.g. [59, 22, 148, 32, 2, 1, 0, 0, 13] @@ -230,61 +224,53 @@ def toHexString(data=[], format=0): '0X3B, 0X65, 0X00, 0X00, 0X9C, 0X11, 0X01, 0X01, 0X03' """ - for byte in tuple(data): - pass - - if type(data) is not list: + if not (data is None or isinstance(data, list)): raise TypeError('not a list of bytes') - if data is None or data == []: + if not data: return "" - else: - pformat = "%-0.2X" - if COMMA & format: - separator = "," + + pformat = "%-0.2X" + separator = "" + if COMMA & format: + separator = "," + if not PACK & format: + separator += " " + if HEX & format: + if UPPERCASE & format: + pformat = "0X" + pformat else: - separator = "" - if not PACK & format: - separator = separator + " " - if HEX & format: - if UPPERCASE & format: - pformat = "0X" + pformat - else: - pformat = "0x" + pformat - return (separator.join(map(lambda a: pformat % ((a + 256) % 256), data))).rstrip() - - -# FIXME This appears to duplicate toASCIIString() -def HexListToBinString(hexlist): - """ + pformat = "0x" + pformat + return separator.join(pformat % (a & 0xff) for a in data).rstrip() + + +def HexListToBinString(hexlist: list[int]) -> str: + """Deprecated. Use `bytes(hexlist).decode("utf-8")` or similar. + >>> HexListToBinString([78, 117, 109, 98, 101, 114, 32, 49, 48, 49]) 'Number 101' """ - return ''.join(map(chr, hexlist)) - -# FIXME This appears to duplicate to ASCIIBytes() -def BinStringToHexList(binstring): - """ - >>> BinStringToHexList("Number 101") - [78, 117, 109, 98, 101, 114, 32, 49, 48, 49] - """ - return list(map(ord, binstring)) + warnings.warn( + 'Use `bytes(hexlist).decode("utf-8")` or similar.', + DeprecationWarning, + ) + return bytes(hexlist).decode("utf-8") -def hl2bs(hexlist): - """An alias for HexListToBinString +def BinStringToHexList(binstring: str) -> list[int]: + """Deprecated. Use `list(binstring.encode("utf-8"))` or similar. - >>> hl2bs([78, 117, 109, 98, 101, 114, 32, 49, 48, 49]) - 'Number 101' + >>> BinStringToHexList("Number 101") + [78, 117, 109, 98, 101, 114, 32, 49, 48, 49] """ - return HexListToBinString(hexlist) + warnings.warn( + 'Use `list(binstring.encode("utf-8"))` or similar.', + DeprecationWarning, + ) + return list(binstring.encode("utf-8")) -def bs2hl(binstring): - """An alias for BinStringToHexList - >>> bs2hl("Number 101") - [78, 117, 109, 98, 101, 114, 32, 49, 48, 49] - """ - return BinStringToHexList(binstring) +hl2bs = HexListToBinString +bs2hl = BinStringToHexList diff --git a/test/test_util.py b/test/test_util.py index 3eba458a..085e3404 100644 --- a/test/test_util.py +++ b/test/test_util.py @@ -128,22 +128,26 @@ def test_toHexString(self): def test_HexListToBinString(self): data_in = [1, 2, 3] data_out = "\x01\x02\x03" - self.assertEqual(HexListToBinString(data_in), data_out) + with self.assertWarns(DeprecationWarning): + self.assertEqual(HexListToBinString(data_in), data_out) def test_BinStringToHexList(self): data_in = "\x01\x02\x03" data_out = [1, 2, 3] - self.assertEqual(BinStringToHexList(data_in), data_out) + with self.assertWarns(DeprecationWarning): + self.assertEqual(BinStringToHexList(data_in), data_out) def test_hl2bs(self): data_in = [78, 117, 109, 98, 101, 114, 32, 49, 48, 49] data_out = 'Number 101' - self.assertEqual(hl2bs(data_in), data_out) + with self.assertWarns(DeprecationWarning): + self.assertEqual(hl2bs(data_in), data_out) def test_bs2hl(self): data_in = 'Number 101' data_out = [78, 117, 109, 98, 101, 114, 32, 49, 48, 49] - self.assertEqual(bs2hl(data_in), data_out) + with self.assertWarns(DeprecationWarning): + self.assertEqual(bs2hl(data_in), data_out) if __name__ == '__main__':